Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

"Subclassing" traits in Rust

I have a situation where several of my structs should implement multiple traits, but all of them implement at least one trait in common. When I get hold of a mixed bag of these structs, I want to treat them all as being of the common trait: pass them as method parameters typed to that trait, store them in collections typed for that trait, etc.

I haven't been able to figure out how to do it. Here is some code where I try to do the way it was suggested here, but it fails to compile:

trait ThingWithKeys {
    fn use_keys (&self) -> String;
}

//////

trait CorrectionsOfficer {
    fn hitch_up_pants (&self) -> String;
}

trait CorrectionsOfficerWithKeys: ThingWithKeys + CorrectionsOfficer {}

struct CorrectionsOfficerReal {}

impl ThingWithKeys for CorrectionsOfficerReal {
    fn use_keys (&self) -> String {
        String::from ("Clank, clank")
    }
}

impl CorrectionsOfficer for CorrectionsOfficerReal {
    fn hitch_up_pants (&self) -> String {
        String::from ("Grunt")
    }
}

impl <T: ThingWithKeys + CorrectionsOfficer> CorrectionsOfficerWithKeys for T {}

//////

trait Piano {
    fn close_lid (&self) -> String;
}

trait PianoWithKeys: Piano + ThingWithKeys {}

struct PianoReal {}

impl ThingWithKeys for PianoReal {
    fn use_keys (&self) -> String {
        String::from ("Tinkle, tinkle")
    }
}

impl Piano for PianoReal {
    fn close_lid (&self) -> String {
        String::from ("Bang!")
    }
}

impl <T: ThingWithKeys + Piano> PianoWithKeys for T {}

//////

trait Florida {
    fn hurricane (&self) -> String;
}

trait FloridaWithKeys: ThingWithKeys + Florida {}

struct FloridaReal {}

impl ThingWithKeys for FloridaReal {
    fn use_keys (&self) -> String {
        String::from ("Another margarita, please")
    }
}

impl Florida for FloridaReal {
    fn hurricane (&self) -> String {
        String::from ("Ho-hum...")
    }
}

impl <T: ThingWithKeys + Florida> FloridaWithKeys for T {}

//////

fn main() {
    let corrections_officer_ref: &CorrectionsOfficerWithKeys = &CorrectionsOfficerReal {};
    let piano_ref: &PianoWithKeys = &PianoReal {};
    let florida_ref: &FloridaWithKeys = &FloridaReal {};

    use_keys (corrections_officer_ref);
    use_keys (piano_ref);
    use_keys (florida_ref);
}

fn use_keys (thing_with_keys: &ThingWithKeys) {
    println! ("{}", thing_with_keys.use_keys ());
}

Here are the compilation errors:

Compiling playground v0.0.1 (file:///playground)
error[E0308]: mismatched types
  --> src/main.rs:80:19
   |
80 |         use_keys (corrections_officer_ref);
   |                   ^^^^^^^^^^^^^^^^^^^^^^^ expected trait `ThingWithKeys`, found trait `CorrectionsOfficerWithKeys`
   |
   = note: expected type `&ThingWithKeys`
              found type `&CorrectionsOfficerWithKeys`

error[E0308]: mismatched types
  --> src/main.rs:81:19
   |
81 |         use_keys (piano_ref);
   |                   ^^^^^^^^^ expected trait `ThingWithKeys`, found trait `PianoWithKeys`
   |
   = note: expected type `&ThingWithKeys`
              found type `&PianoWithKeys`

error[E0308]: mismatched types
  --> src/main.rs:82:19
   |
82 |         use_keys (florida_ref);
   |                   ^^^^^^^^^^^ expected trait `ThingWithKeys`, found trait `FloridaWithKeys`
   |
   = note: expected type `&ThingWithKeys`
              found type `&FloridaWithKeys`

error: aborting due to 3 previous errors

Essentially, it still can't find the ThingWithKeys implementation inside the XxxWithKeys implementations.

like image 431
Dan Wiebe Avatar asked Dec 25 '17 05:12

Dan Wiebe


People also ask

When should I use traits Rust?

We can use traits as function parameters to allow the function to accept any type that can do x , where x is some behavior defined by a trait. We can also use trait bounds to refine and restrict generics, such as by saying we accept any type T that implements a specified trait.

What is the point of traits in Rust?

A trait in Rust is a group of methods that are defined for a particular type. Traits are an abstract definition of shared behavior amongst different types. So, in a way, traits are to Rust what interfaces are to Java or abstract classes are to C++. A trait method is able to access other methods within that trait.

Are Rust traits interfaces?

Rust is not an object oriented language. And traits are not exactly interfaces.

Does Rust have inheritance?

Inheritance as a Type System and as Code Sharing If a language must have inheritance to be an object-oriented language, then Rust is not. There is no way to define a struct that inherits the parent struct's fields and method implementations.


1 Answers

Trait inheritance in Rust differs from OOP inheritance. Trait inheritance is just a way to specify requirements. trait B: A does not imply that if a type implements B it will automatically implement A; it means that if a type implements B it must implement A. This also means that you will have to implement A separately if B is implemented.

As an example,

trait A {}
trait B: A {}

struct S;

impl B for S {}

// Commenting this line will result in a "trait bound unsatisfied" error
impl A for S {}

fn main() {
    let _x: &B = &S;
}

However, if want a type to automatically implement C if it implements A and B (and thereby avoiding manually implementing C for that type), then you can use a generic impl:

impl<T: A + B> C for T {}

In your example, this translates to

impl<T: Florida + ThingWithKeys> FloridaWithKeys for T {}

Take a look at this forum thread for more information.

As an aside, you do not require the ThingWithKeys bound for PianoWithKeys as Piano already requires ThingWithKeys.

EDIT (in accordance with your comment and question edit):

As stated before, trait inheritance in Rust differs from OOP inheritance. Even if trait B: A, you cannot coerce a trait object of B to a trait object of A. If you have no other choice but to pass the trait objects as is to the method, using generics works:

fn use_keys<T: ThingWithKeys + ?Sized>(thing_with_keys: &T) {
    println! ("{}", thing_with_keys.use_keys ());
}

The generic method will work for type references (non trait objects) too.

Also check: Why doesn't Rust support trait object upcasting?

like image 88
EvilTak Avatar answered Nov 18 '22 19:11

EvilTak