我正在try 理解为什么以下代码不能编译:

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的所有 struct 也将继承VehicleDisplay实现.我可以理解,如果Cartry 自己实现Display,为什么这会是一个问题,但这里不是这样.

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

impl std::fmt::Display for dyn Vehicle

impl std::fmt::Display for Car

but for non-trivial examples, this seems really verbose. What's the right way 至 do this?

推荐答案

类型dyn Vehichle与性状Vehichle不同.具体地说,它是所谓的trait object,可以包含实现特征的任何类型的值.

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

如果您希望实现特征TraitA(例如Vehicle)的每个类型也实现特征TraitB(例如Display),那么有两种方法可以实现这一点,每种方法都有一些警告.

第一个是全面实施.由于orphan rule,这只能在TraitB被定义在相同的 crate 中时才能完成,所以这不适用于在标准库中定义的Display.

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

第二种方法是宣布TraitBTraitA的超级性状.即使没有在同一箱中定义TraitB,这也会起作用,但是这将需要实现TraitA的任何特征也实现TraitB,这同样是由于孤立规则对于未在同一箱中定义的类型可能是不可能的.

trait TraitA: TraitB {
    // ...
}


impl TraitA for SomeType {}

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

在任何一种情况下,您都不能在来自不同箱子的类型上实现来自不同箱子的特征,例如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)

虽然这确实变得有点冗长,但它完全避免了孤立规则,因此不存在与try 在每个Vehicle类型上直接实现Display相同的问题.此外,由于Display实现实际上并不与类型本身相关,而是与trait相关,因此通常这可能是解决此问题的一种更惯用的方法.

Rust相关问答推荐

在一个tauri协议处理程序中调用一个rectuc函数的推荐技术是什么?

如何从Rust记录WASM堆内存使用情况?

如何使用Match比较 struct 中的值

文档示例需要导入相关的 struct ,但仅在运行测试时.这是故意的行为吗?

如何将像烫手山芋一样不透明的值从一个Enum构造函数移动到下一个构造函数?

有没有一种惯用的方法来判断VEC中是否存在变体?

循环访问枚举中的不同集合

对于rustc编译的RISC-V32IM二进制文件,llvm objdump没有输出

由于生存期原因,返回引用的闭包未编译

如何获取光标下的像素 colored颜色 ?

try 实现线程安全的缓存

如何强制匹配的返回类型为()?

如何使用tracing-subscriberRust crate 构建多编写者、全局过滤订阅者

Rust 为什么被视为borrow ?

无法将`&Vec>`转换为`&[&str]`

在1.5n次比较中找到整数向量中的最大和次大整数

如何使用 Rust Governor 为每 10 秒 10 个请求创建一个 RateLimiter?

Rust 中的自动取消引用是如何工作的?

如何在 nom 中构建负前瞻解析器?

在 Traits 函数中设置生命周期的问题