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.
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 implementsB
it will automatically implementA
; it means that if a type implementsB
it must implementA
. This also means that you will have to implementA
separately ifB
is implemented.As an example,
However, if want a type to automatically implement
C
if it implementsA
andB
(and thereby avoiding manually implementingC
for that type), then you can use a genericimpl
:In your example, this translates to
Take a look at this forum thread for more information.
As an aside, you do not require the
ThingWithKeys
bound forPianoWithKeys
asPiano
already requiresThingWithKeys
.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 ofB
to a trait object ofA
. If you have no other choice but to pass the trait objects as is to the method, using generics works:The generic method will work for type references (non trait objects) too.
Also check: Why doesn't Rust support trait object upcasting?