【问题标题】:Are polymorphic variables allowed?是否允许多态变量?
【发布时间】:2021-02-17 01:52:35
【问题描述】:

我有各种实现相同特征的结构。我想在某些条件下分支,在运行时决定要实例化哪些结构。然后,无论我遵循哪个分支,我都想从该 trait 调用方法。

这在 Rust 中可能吗?我希望能够实现以下目标(无法编译):

trait Barks {
    fn bark(&self);
}

struct Dog;

impl Barks for Dog {
    fn bark(&self) {
        println!("Yip.");
    }
}

struct Wolf;

impl Barks for Wolf {
    fn bark(&self) {
        println!("WOOF!");
    }
}

fn main() {
    let animal: Barks;
    if 1 == 2 {
        animal = Dog;
    } else {
        animal = Wolf;
    }
    animal.bark();
}

【问题讨论】:

    标签: rust


    【解决方案1】:

    是的,但没那么容易。您在那里写的是animal 应该是Barks 类型的变量,但Barks 是一个特征;一个接口的描述。特征没有静态定义的大小,因为可以出现任何大小的类型和impl Barks。编译器不知道animal 有多大。

    您需要做的是添加一个间接层。在这种情况下,您可以使用Box,但您也可以使用Rc 或普通引用:

    fn main() {
        let animal: Box<dyn Barks>;
        
        if 1 == 2 {
            animal = Box::new(Dog);
        } else {
            animal = Box::new(Wolf);
        }
        
        animal.bark();
    }
    

    在这里,我在堆上分配DogWolf,然后将其转换为Box&lt;dyn Barks&gt;。这有点类似于在 C# 或 Java 中将对象转换为接口,或者在 C++ 中将 Dog* 转换为 Barks*

    您也可以使用完全不同的方法是枚举。你可以有enum Animal { Dog, Wolf },然后定义一个impl Animal { fn bark(&amp;self) { ... } }。取决于您是否需要一组完全开放的动物和/或多种特征。

    最后,请注意上面的“那种”。有很多东西不能像在 Java/C#/C++ 中那样工作。例如,Rust 没有向下转换(你不能从 Box&lt;dyn Barks&gt; 回到 Box&lt;Dog&gt;,或者从一个 trait 到另一个)。此外,这仅在 trait 是“对象安全”的情况下才有效(没有泛型,没有使用 selfSelf 按值)。

    【讨论】:

    • 谢谢!你提到我可以使用简单的引用。这怎么可能?
    • 查看我的答案以获得简单的参考。
    【解决方案2】:

    DK 有一个很好的解释,我将举一个例子,我们在堆栈上分配 DogWolf,避免堆分配:

    fn main() {
        let dog;
        let wolf;
        
        let animal: &dyn Barks = if 1 == 2 {
            dog = Dog;
            &dog
        } else {
            wolf = Wolf;
            &wolf
        };
        
        animal.bark();
    }
    

    这有点难看,但引用完成了与 Box 相同的间接寻址,而且开销更少。

    另见:

    【讨论】:

      【解决方案3】:

      定义自定义枚举是执行此操作的最有效方法。这将允许您在堆栈上准确分配所需的空间量,即最大选项的大小,加上 1 个额外字节来跟踪存储了哪个选项。与使用Box 或特征引用的解决方案不同,它还允许在没有间接级别的情况下直接访问。

      不幸的是,它确实需要更多样板:

      enum WolfOrDog {
          IsDog(Dog),
          IsWolf(Wolf)
      }
      use WolfOrDog::*;
      
      impl Barks for WolfOrDog {
          fn bark(&self) {
              match *self {
                  IsDog(ref d) => d.bark(),
                  IsWolf(ref w) => w.bark()
              }
          }
      }
      
      fn main() {
          let animal: WolfOrDog;
          if 1 == 2 {
              animal = IsDog(Dog);
          } else {
              animal = IsWolf(Wolf);
          }
          animal.bark();
      }
      

      main 中,我们只使用一个堆栈分配变量,保存我们自定义枚举的一个实例。

      【讨论】:

      • 我认为Canine 是枚举的更好名称。
      猜你喜欢
      • 2020-07-31
      • 2019-03-17
      • 2015-11-28
      • 1970-01-01
      • 2013-02-25
      • 1970-01-01
      • 2017-08-03
      • 2012-04-03
      • 2014-07-17
      相关资源
      最近更新 更多