How can polymorphic mixin-like behavior be accomplished in Rust?

Viewed 322

Forgive my "object oriented" mindset (for some value of "object oriented"), but I'm having a hard time figuring out how to create the following analogue in Rust:

In Python, I can define a base class with a method as either abstract/unimplemented ( raise NotImplementedException) or with a default implementation. Then I can create a mixin to override this base behavior. Lastly, I can combine the base class and the mixin into one child class where the mixin method is the method that is bound to the child class. I can do all this in service of trying to fulfill an interface requirement of another piece of code that interfaces with these objects. I want to do this in Rust without using dynamic run-time polymorphism requiring the equivalent of C++ vtables. In Python I would do something like the following with the silly interface do_it:

def do_it(inst):
    inst.must_implement_me()

class Base:
    def must_implement_me(self):
        print('base default implementation')
        
class Mixin:
    def must_implement_me(self):
        print('mixin override new default')

class ChildClass(Mixin, Base):
    pass



do_it(Base())       # prints "base default implementation"
do_it(ChildClass()) # prints "mixin override new default"

When I try to achieve the same goal in Rust:

trait DoIt {
  fn must_implement_me(&self);
}

fn do_it<T: DoIt>(inst: T) {
  inst.must_implement_me();
}


trait Base {}

impl<T> DoIt for T where T: Base {
  fn must_implement_me(&self) {
    println!("base default implementation");
  }
}

trait Mixin {}
impl<T> DoIt for T where T: Mixin {
  fn must_implement_me(&self) {
    println!("mixin override new default");
  }
}

struct BaseClass();
impl Base for BaseClass{}


struct ChildClass();
impl Base for ChildClass {}
impl Mixin for BaseClass {}


fn main() {
  do_it(BaseClass());
  do_it(ChildClass());
}

cargo run complains:

error[E0119]: conflicting implementations of trait `DoIt`:
  --> src/main.rs:20:1
   |
13 | impl<T> DoIt for T where T: Base {
   | -------------------------------- first implementation here
...
20 | impl<T> DoIt for T where T: Mixin + Base {
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ conflicting implementation

Is there a way to tell Rust that the Mixin implementation should win? I understand there is ambiguity, but can I explicitly tell Rust which method implementation wins? If not, how else could this be achieved?

1 Answers

The code above indeed is not possible in rust. Comparing to python there's no trait bounds order, therefore you cannot determine which implementation should be called (e.g. from Base or from Mixin). Your particular issue can be solved with default implementation for trait [doc link] (playground):

trait DoIt {
    fn must_implement_me(&self) {
        println!("base default implementation");
    }
}

// ...

struct BaseClass();
impl Mixin for BaseClass {}

struct ChildClass();
impl DoIt for ChildClass {}

A little bit more complex static "overloadings" can be achieved soon (hopefully) with a nightly-feature (playground):

#![feature(specialization)]

impl<T> DoIt for T {
    default fn must_implement_me(&self) {
        println!("base default implementation");
    }
}

// ...


struct BaseClass();
impl Mixin for BaseClass {}

struct ChildClass();

Nevertheless, even if you are fine with a nightly compiler, I'd rather suggest to avoid the last code example, since that feature is incomplete yet.

Related