Implement TraitA for all types that implement TraitB
Asked Answered
R

2

6

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?

Raving answered 4/2, 2023 at 18:59 Comment(1)
I'm not sure Rust currently supports this kind of "inheritance". A trait can't force another trait on something. It's worth noting that instead of a parent-child relation, Rust tends towards enums like Vehicle::Car(c) or Vehicle::Motorbike(m) each of wraps another type and can implement Display at a higher level. You could also have VehicleContainer<V> where V : Vehicle and that Vehicle is some kind of trait as you have here, though the container is what implements Display.Thirtyeight
S
3

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));
}

(Playground link)

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.

Sulphanilamide answered 4/2, 2023 at 20:45 Comment(1)
This answer should be the canonical duplicate of questions like that!Tait
S
2

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);
}

https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=34f17655e85b3039c327846f5b8a9568

Sordino answered 4/2, 2023 at 20:44 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.