为什么我不能将具有扩展特征的Box转换为具有基本特征的Box?

时间:2018-03-28 20:39:18

标签: casting rust polymorphism traits

给出代码

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。有没有办法做到这一点?如果没有,例如存储具有相同基本特征的不同特征的矢量的常用方法是什么?

1 个答案:

答案 0 :(得分:2)

简短回答是因为traits are not interfaces

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

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

  1. 如果在&Derived的vtable中首先列出了Base的方法,那么您可以将&Derived投射到&Derived,这样就行了。但是,&Base&Derived vtables有不同的长度,这样做会切断&Base末尾的所有内容。因此,如果您尝试在该对象上调用方法,该方法属于&Baseyou'll invoke undefined behavior

  2. 您可以运行一些魔术代码来分析Derived&Base的定义,并能够从&Derived构建&Base的vtable。这将需要在运行时有关这些类型及其布局的附加信息。除了额外的内存使用量之外,这还将具有非零的性能成本。 Rust的基本原则之一是“零成本抽象”,这通常意味着潜在的昂贵操作是明确的而不是隐含的(如果&Derived这样做,通常会被认为过于隐含)。

  3. 一般来说很难说一个更好的模式是什么。如果您要为一组封闭的项目建模,那么枚举通常是更好的方法:

    let a: Box<Base> = b;

    如果您尝试执行更复杂的操作,Entity Component Systems specs可以提供比简单枚举更多的灵活性。