“Subclassing” traits in Rust

2019-08-04 10:29发布

问题:

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.

回答1:

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?