Easyway to use inherit pattern

I'm using some inherit pattern in my project and I feel really painful to do it, it's not painful because how to implement this pattern, it is painful that I have to write lots of similar or even same codes every time I need define a "sub type":

struct Super {}
impl Interface for Super {
    fn f1(&self) {}
    fn f2(&self) {}
}

struct Sub1(Super);
impl Interface for Sub1 {
    fn f1(&self) {
        self.0.f1()
    }

    fn f2(&self) {
        self.0.f2()
    }
}

The implementation of Sub1 is redundancy, but I can't write a generic derive macro for it because the derive macro can not know what function that trait Interface has.

It would be more painful if Sub1 also implements other traits, the deeper the hierarchy is, the more redundancy code I have to write.

It looks to me that it would be good to auto implement traits if using Deref and DerefMut, but since this can't be done, it must have a reason.

So how do you solve this problem, if you are also using inherit pattern?

1 Like

I personally learned not to mind the boilerplate too much, as it is easy to reason about. Alternatively there are crates like delegate that remove some of the boilerplate of forwarding methods to struct fields or enum variants. This won't work for trait implementations though.

I'm not sure I follow. Is the trait Foo the same as the Interface trait in your example? Why exactly can't you write a #[derive(Interface)] for Sub1? I haven't written something like it, but I could imagine the inherit/delegation pattern can be expressed as a derive macro with a helper attribute. Something like this:

#[derive(Interface)]
#[interface(forward_to = "self.0")]
struct Sub(Super);

where the forward_to argument tells the derive macro which field it should forward the implementation of Interface to.

1 Like

That's a typo, I do mean Interface, I updated my question. :rofl:.

I mean that I want to write a general purpose derivce macro, not just for trait Interface.

You're quite right, a macro won't solve the delegation problem, since macros are expanded too early in the compile process for that.

So the real answer is: this would take a language feature to properly solve. It's been suggested before, and I'd like to see it happen, but the language and compiler teams seem to have other priorities ATM. That said, it never hurts to open a topic about it over on IRLO.

There's still lang interest in it, as far as I know.

It needs both a vision for a long-term future, as well as a feasible short-term restricted subset of that future that's useful without closing off possibilities.

(In general, there's a ton of awkward questions around things like associated types -- what does it mean to delegate ops::Add, for example? -- so carefully just not supporting such things might be the most practical way to get something to happen for now. And maybe for now it only supports delegating to fields, but with syntax that's not ambiguous with delegating to something else in the future.)

1 Like

I think a great 80/20 solution would be exactly to target field delegation first, and then traits; those seem to be the varieties (in that order) that people most ask for.
More advanced forms of delegation (which likely raise more open design questions for now and thus Indeed need to be considered more carefully) could come later.

Absolutely, yes. It's just important that the syntax be, say, delegate * to self.a; instead of delegate * to a;, so that it can be extended to more things in the future without ambiguity -- imagine there was a const a: usize in scope, for example, which wouldn't matter if it only supported field delegation, but would if it was extended to support more stuff later.

2 Likes

I wonder if this helps and/or is a good approach:

pub struct Super {}

mod private {
    use super::*;
    pub trait AsSuper { 
        fn as_super(&self) -> &Super;
        fn f1(&self) { /* can use `self.as_super()` */ }
        fn f2(&self) { /* can use `self.as_super()` */ }
    }
}
use private::AsSuper;

pub trait Interface {
    fn f1(&self);
    fn f2(&self);
}

impl<T: AsSuper> Interface for T {
    fn f1(&self) {
        <Self as AsSuper>::f1(self)
    }
    fn f2(&self) {
        <Self as AsSuper>::f2(self)
    }
}

pub struct Sub1(Super);
impl AsSuper for Sub1 {
    fn as_super(&self) -> &Super {
        &self.0
    }
}

pub struct Sub2(Super);
impl AsSuper for Sub2 {
    fn as_super(&self) -> &Super {
        &self.0
    }
    fn f2(&self) {
        println!("I choose not to delegage.");
    }
}

(Playground)

FWIW, the ambassador crate is my current favorite for this specific use case, if you want some macro-driven sugar. Any macro solution will necessarily require annotating the trait as well. ambassador emits a macro_rules! macro alongside your trait which is used to implement the actual delegation.

With ambassador, the OP example would look like

use ambassador::{delegatable_trait, Delegate};

#[delegatable_trait]
trait Interface {
    fn f1(&self);
    fn f2(&self);
}

struct Super {}
impl Interface for Super {
    fn f1(&self) {}
    fn f2(&self) {}
}

#[derive(Delegate)]
#[delegate(Interface)]
struct Sub1(Super);

(A derive macro is used because it's nicer to IDEs, since it cannot change the struct definition like a non-derive attribute macro could.)

8 Likes

Can this crate be used cross projects? For example, the Interface trait is in A, and B crate depends on it and want to do the delegate thing.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.