Simulating inheritance in rust

in #rust-lang7 years ago

Simulating inheritance in rust already is possible in some cases.
It depends on specialization, generic structs and traits.
It's not only useful for inheritance but more powerful for generally composing functionalities.
This kind of inheritance should be a zero cost abstraction.
I'll explain here, how it works.

Every struct, that may inherit methods from a parent, has to save the parent type inside the struct.

The struct needs to implement traits to get the value of the parent field like this:

pub trait Inherit {
    type Parent;

    fn parent(&self) -> &Self::Parent;
}

pub trait InheritMut: Inherit {
    fn parent_mut(&mut self) -> &mut Self::Parent;
}

The implementation of a child struct may look like this:

struct Child<P,T> {
    parent: P,
    other_values: T // more fields
}

impl<P,T> Inherit for Child<P,T> {
    type Parent = P;

    #[inline]
    fn parent(&self) -> &Self::Parent {
        &self.parent
    }
}

impl<P,T> InheritMut for Child<P,T> {
    #[inline]
    fn parent_mut(&mut self) -> &mut Self::Parent {
        &mut self.parent
    }
}

These implementations of Inherit and InheritMut should be derivable, at least for structs with a single field.

Traits with methods, that can be inherited, need a default implementation.
A default implementation may look like this:

trait Test {
    fn test(&self);
    … // more fields or functions will also work
}

default impl<T> Test for T where T: Inherit, <T as Inherit>::Parent: Test {
    #[inline]
    fn test(&self) {
        self.parent().test()
    }
}

For traits containing functions for mutable references to self, the default implementation will be a bit different:

trait TestMut {
    fn test_mut(&mut self);
    … // more fields or functions will also work
}

default impl<T> TestMut for T where T: Inherit, <T as Inherit>::Parent: TestMut {
    #[inline]
    fn test_mut(&mut self) {
        self.parent_mut().test_mut()
    }
}

Now if some struct implements Inherit (and InheritMut), it will by default implement the traits of the parent.

If the struct doesn't implement Inherit, there is no problem with implementing these traits for the struct.

Use cases

There are multiple use cases for this kind of inheritance.

Multiinheritance

It's possible to inherit multiple times from the same parent object:

trait Mass<T> {
    fn mass(&self) -> T
}

impl<P,T> Mass for Child<P,T> where T: Copy+Add<Output=T>, P: Mass {
    #[inline]
    fn mass(&self) {
        self.other_value+self.parent().mass();
    }
}

This example is not that useful, but there are cases, where this simplyfies things very much.
For example you could define a type, whose child can transform a value and when updating it transforms it's parent by the value. Having one child would mean displacement, having a child, that depens on a child, it would be acceleration, and so on.

Fake multiple inheritance

Even if it looks as if only single inheritance is supportet, it would even support multiple inheritance, at least something, that is almost as powerful.
Instead of inheriting from multiple super types at once, you just need to inherit from a single super type which inherits from the other super type.
They may both have specialized implementations for the super type.

Reverse inheritance

Unlike traditional inheritance it's also possible to define a child and switch the parent type.
For example, when some crate defiens a child type which accelerates the parent type and a parent type, which just saves the position, it would be possible to define a new parent type, which can be accelerated in a different way, for example by accelerating all object inside a set of acceleratable objects.

Composing functionalities

It can also be seen as a general way of composing functionalities instead of inheritance.
That's what probably describes it better than inheritance, because you never define explicite hierarchies of types, but implicitely create these types by creating values of these types without the need of ever specifying the real types.
Because the types can be deeply nested, it will be useful, to define all functions based on generics then, so you almost never have to use some types themselves.

Advantages

The main advantage is, that everything can be as efficient as if handcoded in the efficient way oneself.
But this also forces the user to define the code in a generic way and makes it more extensible.
It's possible to define functions on the types, but it's probably not useful, when having very deep hierarchic structs of wrapping types.

The structs will be composed into a single struct, which should look in memory like the struct, you would have composed yourself.
Even the functions should look as if defined by you, when using function inlining.

Problems

But there is a problem:
If a struct implements Inherit and wants to add a new trait, which also has a default implementation, then it requires the parent to implement that trait, too.

In some cases this would just result in a super type, which does not inherit and so is able to implement the needed traits, which will be used by the childs as default implementation.
But in most cases these default implementations should not be called and may be implemented to just panic when called, which is not as secure, as one would want to.
In some cases it's not even possible, when the methods depend on generics, but the super type isn't generic, which would result in unconstrained types.

Alternatives

Instead of using default implementations, it would also be a good way to just implement these default implementations for the specific types.
These implementations could also just be derived, but it requires to define a custom derive for every trait, which is not trivial yet.
This approach also does not allow some child traits of another crate to use my trait as parent.

Conclusion

If the problems can be fixed, this may be a nice way to write extensible generic code more powerful than inheritance, but this way it's a bit annoying in some cases.
I'm happy, if someone has better ideas to do similar things.