How to implement inheritance-like feature for Rust?

The most natural way to handle this in a way that scales to large function counts with the least amount of boilerplate in your "derived" types:

mod apple_eater {
    pub struct AppleEater {
        granny_smiths: u32,
        golden_delicious: u32,
        ...
    }

    impl AppleEater {
        pub fn report(&self) {
            println!("I ate {} granny smiths!", self.granny_smiths);
            println!("...and {} golden delicious!", self.golden_delicous);
            ...
        }

        pub fn eat_granny_smith(&mut self) { self.granny_smiths += 1; }
        pub fn eat_golden_delicious(&mut self) { self.golden_delicious += 1; }
        ...
    }

    pub trait AsAppleEater {
        fn as_apple_eater(&self) -> &AppleEater;

        fn report(&self)
        { self.as_apple_eater().report() }
    }

    pub trait AsMutAppleEater: AsAppleEater {
        fn as_mut_apple_eater(&mut self) -> &mut AppleEater;

        fn eat_granny_smith(&mut self)
        { self.as_mut_apple_eater().eat_granny_smith() }

        fn eat_golden_delicious(&mut self)
        { self.as_mut_apple_eater().eat_golden_delicious() }
    }
}

Now types only need to implement two functions:

impl AsAppleEater for Teacher {
    fn as_apple_eater(&self) -> &AppleEater
    { &self.apple_eater }
}

impl AsMutAppleEater for Teacher {
    fn as_mut_apple_eater(&mut self) -> &mut AppleEater
    { &mut self.apple_eater }
}

and as a bonus, you can choose not to implement AsMutAppleEater for some types:

impl AsAppleEater for PersonWhoJustAte {
    fn as_apple_eater(&self) -> &AppleEater
    { &self.apple_eater }
}

// no AsMutAppleEater; the person is too full to eat more!

(similarly, this solves the circle/ellipse problem. A circle can implement AsEllipse if implemented that way, but it cannot implement AsMutEllipse)


In the case of single inheritance, this boilerplate can be significantly reduced using Deref/DerefMut instead of custom traits, as auto-deref will automatically forward all methods. But using Deref for this purpose is generally discouraged, as Deref is intended for pointer types. Even things as innocent as impl Deref<Target=CStr> for CString have led to problems like the ability to call as_ptr() on a temporary CString.

5 Likes

THANKS! This seems a lot better. In that way, I think it fits better the "has-a" pattern rather than "is a" pattern that inheritance is based on. It will be a bit verbose since we split the behaviours and data that the behaviours are based on into a trait(actually two for the purpose of encapsulation I think) and a struct, while inheritance keeps them as a whole, but it is just a trade-off, I think. This solves circle/ellipse problem, after all.

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.