Reputation: 1428
I am trying to understand why the following code does not compile:
trait Vehicle {
fn get_num_wheels(&self) -> u32;
}
impl std::fmt::Display for dyn Vehicle {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Has {} wheels", self.get_num_wheels())
}
}
struct Car();
impl Vehicle for Car {
fn get_num_wheels(&self) -> u32 {
4
}
}
fn main() {
let car = Car {};
println!("{car}");
}
error[E0277]: `Car` doesn't implement `std::fmt::Display`
--> src/main.rs:21:16
|
21 | println!("{car}");
| ^^^ `Car` cannot be formatted with the default formatter
|
= help: the trait `std::fmt::Display` is not implemented for `Car`
I would think that if I implemented Display
for Vehicle
, then all structs that implement Vehicle
would also inherit Vehicle
's implementation of Display
. I can see why this would be an issue if Car
tried to make its own implementation of Display
, but this is not the case here.
I know I can fix this example by changing
impl std::fmt::Display for dyn Vehicle
to
impl std::fmt::Display for Car
but for non-trivial examples, this seems really verbose. What's the right way to do this?
Upvotes: 6
Views: 115
Reputation: 40914
The type dyn Vehichle
is not the same as the trait Vehichle
. Specifically, it's what's called a trait object, and can hold a value of any type that implements the trait.
Therefore, when you implement Display for dyn Vehichle
, you only implement it for that particular type, but not for any other type that implements the trait.
If you want every type that implements a trait TraitA
(e.g. Vehicle
) to also implement a trait TraitB
(e.g. Display
), there are two ways of approaching this, each with some caveats.
The first is to have a blanket implementation. Due to the orphan rule, this can only be done if TraitB
is defined in the same crate, so this wouldn't work with Display
which is defined in the standard library.
impl<T: TraitA> TraitB for T {
// ...
}
The second approach is to declare TraitB
a supertrait of TraitA
. This will work even if TraitB
is not defined in the same crate, however this will require any trait that implements TraitA
to also implement TraitB
, which again due to the orphan rule may not be possible for types that are not defined in the same crate.
trait TraitA: TraitB {
// ...
}
impl TraitA for SomeType {}
// required by the above, else the compiler will complain
impl TraitB for SomeType {}
In either case, you will not be able to implement a trait from a different crate, such as Display
, on a type from a different crate. The first approach won't work for your code, but the second can work since the Car
type is defined in the same crate.
An approach to circumvent the issue entirely is instead to have a "displayable wrapper type" that can wrap any type that implements Vehicle
. For example:
struct DisplayVehicle<'a, V: ?Sized + Vehicle>(&'a V);
impl<'a, V: ?Sized + Vehicle> std::fmt::Display for DisplayVehicle<'a, V> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Has {} wheels", self.0.get_num_wheels())
}
}
fn main() {
let car = Car {};
println!("{}", DisplayVehicle(&car));
}
While this does become slightly more verbose, it avoids the orphan rule entirely and therefore doesn't have the same issues as trying to implement Display
directly on every Vehicle
type. Additionally, since the Display
implementation isn't actually related to the type itself but to the trait, this may be generally a more idiomatic approach to the solve this problem.
Upvotes: 3
Reputation: 9657
It works if you cast directly to the trait object btw:
trait Vehicle {
fn get_num_wheels(&self) -> u32;
}
impl std::fmt::Display for dyn Vehicle {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Has {} wheels", self.get_num_wheels())
}
}
struct Car();
impl Vehicle for Car {
fn get_num_wheels(&self) -> u32 {
4
}
}
fn main() {
let car = Car {};
println!("{}", &car as &dyn Vehicle);
}
Upvotes: 2