代码之家  ›  专栏  ›  技术社区  ›  bofjas

为什么我不能将一个具有扩展特征的盒子转换为一个具有基本特征的盒子?[副本]

  •  1
  • bofjas  · 技术社区  · 6 年前

    给定代码

    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;
    }
    

    当我编译as时,我确信您知道我得到了以下错误消息:

    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>`
    

    为什么不允许我这样做?如果一个盒子包含一个三角形,则可以保证它也包含一个底部。有没有办法做到这一点?如果没有,那么存储不同特征向量的常用方法是什么,例如,所有这些特征都具有相同的基本特征?

    1 回复  |  直到 6 年前
        1
  •  2
  •   Wesley Wiser    6 年前

    简单的回答是因为 traits are not interfaces

    答案很长,因为 &Base 特征对象和 &Derived 特质对象不是一回事。这个 vtables 不同之处在于 Derived Base 是不同的特征。的vtable 派生的 将包括所有 Dervied 的方法以及所有 基础 的vtable &基础 仅包括 基础 的方法。

    现在,很明显, 基础 的方法 在里面 &派生的 的vtable。因此,也许你可以做一些聪明的事情,获得你想要的行为:

    1. 如果 基础 的方法列在 &派生的 是vtable,那么您可以 &派生的 &基础 这就行了。然而 &派生的 &基础 vtables有不同的长度,这样做会切掉 &基础 。因此,如果您尝试对作为 派生的 ,则, you'll invoke undefined behavior

    2. 您可以运行一些神奇的代码来分析 &基础 &派生的 并能够为 &基础 从…起 &派生的 。这需要在运行时提供有关这些类型及其布局的其他信息。除了额外的内存使用之外,这还将带来非零的性能成本。Rust的基本原则之一是“零成本抽象”,这通常意味着潜在的昂贵操作是显式的,而不是隐式的(如果 let a: Box<Base> = b; 如果这样做,通常会被认为过于含蓄)。


    一般来说,很难说什么是更好的模式。如果要对一组封闭的项进行建模,枚举通常是一种更好的方法:

    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 喜欢 specs 可以给您比简单枚举更大的灵活性。