为所有实现 TraitB 的类型实现 TraitA

bcd*_*dan 6 traits rust

我试图理解为什么以下代码无法编译:

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}");
}
Run Code Online (Sandbox Code Playgroud)
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`
Run Code Online (Sandbox Code Playgroud)

我认为如果我实现了Displayfor Vehicle,那么所有实现的结构Vehicle也将继承Vehicle的实现DisplayCar我可以理解为什么如果尝试自己实现,这会成为一个问题Display,但这里的情况并非如此。

我知道我可以通过改变来修复这个例子

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`
Run Code Online (Sandbox Code Playgroud)

impl std::fmt::Display for dyn Vehicle
Run Code Online (Sandbox Code Playgroud)

但对于重要的例子来说,这似乎非常冗长。这样做的正确方法是什么?

Frx*_*rem 3

类型dyn Vehichle与特征不同Vehichle。具体来说,它就是所谓的特征对象,并且可以保存实现该特征的任何类型的值。

因此,当您实现 时Display for dyn Vehichle,您仅针对该特定类型实现它,而不是针对实现该特征的任何其他类型。

如果您希望每个实现特征TraitA(例如Vehicle)的类型也实现特征TraitB(例如Display),有两种方法可以实现这一点,每种方法都有一些注意事项。

一是全面落实。由于孤儿规则,只有在同一个板条箱中定义时才能执行此操作TraitB,因此这不适用于Display标准库中定义的情况。

impl<T: TraitA> TraitB for T {
    // ...
}
Run Code Online (Sandbox Code Playgroud)

第二种方法是声明 的TraitB超级特征TraitA。即使TraitB未在同一个板条箱中定义,这也将起作用,但这将需要实现的任何特征TraitA也实现TraitB,这又由于孤儿规则对于未在同一个板条箱中定义的类型来说可能是不可能的。

trait TraitA: TraitB {
    // ...
}


impl TraitA for SomeType {}

// required by the above, else the compiler will complain
impl TraitB for SomeType {}
Run Code Online (Sandbox Code Playgroud)

在任何一种情况下,您都无法在来自不同板条箱的Display类型上实现来自不同板条箱的特征,例如 。第一种方法不适用于您的代码,但第二种方法可以使用,因为Car类型是在同一个包中定义的。


完全规避该问题的一种方法是使用“可显示包装类型”,它可以包装任何实现Vehicle. 例如:

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));
}
Run Code Online (Sandbox Code Playgroud)

游乐场链接

虽然这确实变得稍微冗长一些,但它完全避免了孤儿规则,因此不会出现与尝试Display直接在每种Vehicle类型上实现相同的问题。此外,由于Display实现实际上与类型本身无关,而是与特征相关,因此这通常可能是解决此问题的更惯用的方法。