Home > Software engineering >  Multiple inheritance for OOP-like Rust
Multiple inheritance for OOP-like Rust

Time:08-02

I want to implement an OOP approach in Rust. My base class BaseClass would look like this (but with more parameters):

struct BaseClass {
    name: String,
}

impl BaseClass {
    fn new(name: &str) -> Self {
        Self{name: name.to_string()}
    }
}

This base class has an associated trait that does the trick for inheritance:

trait BaseClassInterface {
    fn as_base(&self) -> &BaseClass;
    fn get_name(&self) -> &str {
        &self.as_base().name
    }
}

Now, I want to inherit from this base class and add extra stuff with generics:

struct MiddleClass<T> {
    base: BaseClass,
    value: T,
}

impl<T> MiddleClass<T> {
    fn new(name: &str, value: T) -> Self {
        Self{base: BaseClass::new(name), value}
    }
}

Again, this is an "abstract" class. Users of my library will "inherit" from this middle class to define their structs. So let's do a trait for it:

trait MiddleClassInterface {
    type Type;
    fn as_middle(&self) -> &MiddleClass<Self::Type>;
    fn get_value(&self) -> &Self::Type {
        &self.as_middle().value
    }
}

Now we implement the BaseClassInterface trait for the MiddleClassInterface trait and we achieved "class inheritance"!

impl<T> BaseClassInterface for dyn MiddleClassInterface<Type = T> {
    fn as_base(&self) -> &BaseClass {
        &self.as_middle().base
    }
}

So now, users can implement their own versions of the MiddleClassInterface and use methods from the BaseClassInterface trait:

struct MyIntClass {
    middle: MiddleClass<i32>,
}

impl MyIntClass {
    fn new(name: &str, value: i32) -> Self {
        Self{middle: MiddleClass::new(name, value)}
    }
}

impl MiddleClassInterface for MyIntClass {
    type Type = i32;
    fn as_middle(&self) -> &MiddleClass<Self::Type> {
        &self.middle
    }
}

As MyIntClass implements the MiddleClassInterface, MyIntClass will implement the BaseClassInterface... or not? Let's look at my main function:

fn main() {
    let my_class = MyIntClass::new("my_class", 1);
    println!("{}", my_class.get_name());
}

When compiling, I get the following error:

error[E0599]: no method named `get_name` found for struct `MyIntClass` in the current scope
  --> src/main.rs:71:29
   |
52 | struct MyIntClass {
   | ----------------- method `get_name` not found for this
...
71 |     println!("{}", my_class.get_name());
   |                             ^^^^^^^^ method not found in `MyIntClass`
   |
   = help: items from traits can only be used if the trait is implemented and in scope
note: `BaseClassInterface` defines an item `get_name`, perhaps you need to implement it
  --> src/main.rs:13:1
   |
13 | trait BaseClassInterface {
   | ^^^^^^^^^^^^^^^^^^^^^^^^

For more information about this error, try `rustc --explain E0599`.

MyIntClass implements the MiddleClassInterface<i32> class, which in turn implements the BaseClassInterface trait. So... why does MyIntClass not implement the BaseClassInterface indirectly?

Thanks in advance!

Playground: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=3aa8007b440b965aedf124a69dacc6f7

CodePudding user response:

Your code will work if you change:

impl<T> BaseClassInterface for dyn MiddleClassInterface<Type = T> {

to:

impl<T> BaseClassInterface for T where T: MiddleClassInterface {

What you did above is implement BaseClassInterface for a trait object but such implementations are not considered for method lookup on concrete types. You would first have to coerce my_class into a trait for it to work:

(&my_class as &dyn MiddleClassInterface<Type=i32>).get_name())

Whereas the fix is to not implement BaseClassInterface for a trait object, but rather implement it for all types that implement the MiddleClassInterface. The difference being that MyIntClass will itself implement the base class and won't have to go through dynamic dispatch to call those methods.

  • Related