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

6

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

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`

我认为如果我针对Vehicle实现了Display,那么所有实现Vehicle的结构体也将继承VehicleDisplay的实现。我可以看出,如果Car试图制作自己的Display实现,这将是一个问题,但在这种情况下并非如此。
我知道我可以通过更改来修复此示例。
impl std::fmt::Display for dyn Vehicle

impl std::fmt::Display for Car

但对于非平凡的示例,这似乎非常冗长。有什么正确的方法来做到这一点?


2
我不确定 Rust 目前是否支持这种“继承”。一个 trait 不能强制另一个 trait 在某些东西上。值得注意的是,Rust 倾向于枚举类型,例如 Vehicle::Car(c)Vehicle::Motorbike(m),每个枚举都包装了另一种类型,并可以在更高级别上实现 Display。您还可以拥有 VehicleContainer<V> where V : Vehicle,其中 Vehicle 是某种 trait,就像您在这里看到的那样,尽管容器是实现 Display 的部分。 - tadman
2个回答

3

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

因此,当您为dyn Vehichle实现Display时,您只为该特定类型实现它,而不是为实现该特质的任何其他类型实现它。

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

第一种方法是使用全局实现。由于孤儿规则,只有在相同的crate中定义了TraitB,才能执行此操作,因此无法使用标准库中定义的Display

impl<T: TraitA> TraitB for T {
    // ...
}

第二种方法是将TraitB声明为TraitA的超级trait。即使TraitB未在同一crate中定义,这也可以工作,但这将要求实现TraitA的任何trait也要实现TraitB,由于孤儿规则的限制,对于未在同一crate中定义的类型可能不可能实现这些trait。
trait TraitA: TraitB {
    // ...
}


impl TraitA for SomeType {}

// required by the above, else the compiler will complain
impl TraitB for SomeType {}

无论哪种情况,您都无法在来自不同包的类型上实现一个 trait,例如 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));
}

(Playground link)

虽然这样会变得稍微冗长一些,但它完全避免了孤立规则,并且因此不像直接在每个Vehicle类型上实现Display那样存在问题。此外,由于Display实现实际上与类型本身无关而与trait有关,因此这可能是解决此问题的更通用的惯用方法。


这个回答应该是类似问题的规范重复! - Chayim Friedman

2

如果您直接将其转换为trait对象,则它可以正常工作:

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接