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.
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.
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.
Rust is not an object oriented language. And traits are not exactly interfaces.
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.
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?
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With