为什么我不能将具有扩展特征的 Box 投射到具有基本特征的 Box?

Why can't I cast a Box with an extended trait to a Box with the base trait?

给定代码

trait Base { }

trait Derived : Base { }

struct Foo { }

impl Base for Foo { }

impl Derived for Foo { }

fn main()
{
    let b : Box<Derived> = Box::new( Foo { } );
    let a : Box<Base> = b;
}

当我确定您知道我进行编译时,我收到以下错误消息:

error[E0308]: mismatched types
  --> src/main.rs:14:25
   |
14 |     let a : Box<Base> = b;
   |                         ^ expected trait `Base`, found trait `Derived`
   |
   = note: expected type `std::boxed::Box<Base>`
              found type `std::boxed::Box<Derived>`

为什么不允许我这样做?如果一个 Box 包含一个 Dervied,则保证它也包含一个 Base。有什么办法吗?如果不是,例如存储具有相同基本特征的不同特征的向量的常用方法是什么?

简短的回答是因为 .

长答案是因为 &Base 特征对象和 &Derived 特征对象不是一回事。 vtables 是不同的,因为 DerivedBase 是不同的特征。 Derived 的 vtable 将包括 Dervied 的所有方法以及 Base 的所有方法,而 &Base 的 vtable 将仅包括 Base'方法。

现在,显然,Base 的方法 &Derived 的 vtable 中。所以也许你可以做一些聪明的事情并获得你想要的行为:

  1. 如果 Base 的方法在 &Derived 的 vtable 中排在第一位,那么您可以将 &Derived 转换为 &Base 并且那行得通。但是,&Derived&Base vtables 有不同的长度,这样做会切断 &Base 结束后的所有内容。因此,如果您尝试调用属于 Derivedyou'll invoke undefined behavior 的对象的方法。

  2. 你可以 运行 一些神奇的代码来分析 &Base&Derived 的定义,并能够为 &Base 构造一个 vtable来自 &Derived。这将需要在 运行 时提供有关这些类型及其布局的其他信息。除了额外的内存使用外,这还会产生 non-zero 性能成本。 Rust 的基本原则之一是 "zero cost abstractions",这通常意味着潜在的昂贵操作是显式的而不是隐式的(如果 let a: Box<Base> = b; 这样做,通常会被认为过于隐式)。


很难笼统地说什么是更好的模式。如果您正在为 closed-set 项建模,枚举通常是更好的方法:

enum Animal {
  Dog { name: String, age: u8 },
  Cat { name: String, age: u8, sleeping: bool },
  Fish { name: String, age: u8, in_ocean: bool },
}

如果您想做一些更复杂的事情,Entity Component Systems like specs 可以比简单的枚举给您更多的灵活性。