【问题标题】:Vector of objects belonging to a trait属于特征的对象的向量
【发布时间】:2014-09-12 23:02:57
【问题描述】:

考虑以下代码:

trait Animal {
    fn make_sound(&self) -> String;
}

struct Cat;
impl Animal for Cat {
    fn make_sound(&self) -> String {
        "meow".to_string()
    }
}

struct Dog;
impl Animal for Dog {
    fn make_sound(&self) -> String {
        "woof".to_string()
    }
}

fn main () {
    let dog: Dog = Dog;
    let cat: Cat = Cat;
    let v: Vec<Animal> = Vec::new();
    v.push(cat);
    v.push(dog);
    for animal in v.iter() {
        println!("{}", animal.make_sound());
    }
}

当我尝试推送cat(类型不匹配)时,编译器告诉我vAnimal 的向量

那么,我怎样才能创建一个属于一个特征的对象向量并在每个元素上调用相应的特征方法呢?

【问题讨论】:

    标签: polymorphism rust


    【解决方案1】:

    Vec&lt;Animal&gt; 是不合法的,但编译器不能告诉你,因为类型不匹配以某种方式隐藏了它。如果我们删除对push 的调用,编译器会给我们以下错误:

    <anon>:22:9: 22:40 error: instantiating a type parameter with an incompatible type `Animal`, which does not fulfill `Sized` [E0144]
    <anon>:22     let mut v: Vec<Animal> = Vec::new();
                      ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    

    这不合法的原因是Vec&lt;T&gt; 在内存中连续存储了许多T 对象。但是,Animal 是一个特征,并且特征没有大小(CatDog 不保证大小相同)。

    为了解决这个问题,我们需要在Vec 中存储一些有大小的东西。最直接的解决方案是将值包装在Box 中,即Vec&lt;Box&lt;Animal&gt;&gt;Box&lt;T&gt; 具有固定大小(如果 T 是特征,则为“胖指针”,否则为简单指针)。

    这是一个有效的main

    fn main() {
        let dog: Dog = Dog;
        let cat: Cat = Cat;
        let mut v: Vec<Box<Animal>> = Vec::new();
        v.push(Box::new(cat));
        v.push(Box::new(dog));
        for animal in v.iter() {
            println!("{}", animal.make_sound());
        }
    }
    

    【讨论】:

    • 这似乎在使用 trait 对象,即动态调度。然而,the Rust Programming Language book on this topic 说“总是可以有一个瘦的静态分派包装函数来进行动态分派,但反之亦然,这意味着静态调用更加灵活。”实际上有没有办法在这种情况下不使用特征对象而只执行静态调度?我还遇到了一个用例,我希望有一个向量存储实现相同特征的可能不同类型的结构。
    • @JIXIang 如果您事先知道将在Vec 中放入哪些具体类型,则可以使用enum 代替(然后该特征可能会变得多余)。否则,不,您无法避免动态调度。请记住,动态调度本质上并不是邪恶的;在某些情况下可以使用它!
    • 现在首选的写法是Box&lt;dyn Animal&gt;
    • @Boiethios 现在是必需的。
    【解决方案2】:

    您可以使用引用特征对象&amp;Animal 借用元素并将这些特征对象存储在Vec 中。然后你可以枚举它并使用 trait 的接口。

    通过在特征前面添加&amp; 来更改Vec 的泛型类型将起作用:

    fn main() {
        let dog: Dog = Dog;
        let cat: Cat = Cat;
        let mut v: Vec<&Animal> = Vec::new();
        //             ~~~~~~~
        v.push(&dog);
        v.push(&cat);
        for animal in v.iter() {
            println!("{}", animal.make_sound());
        }
        // Ownership is still bound to the original variable.
        println!("{}", cat.make_sound());
    }
    

    如果您可能希望原始变量保留所有权并在以后重用它,这很好。

    请记住,对于上述情况,您不能转让dogcat 的所有权,因为Vec 在相同范围内借用了这些具体实例。

    引入新的作用域有助于处理这种特殊情况:

    fn main() {
        let dog: Dog = Dog;
        let cat: Cat = Cat;
        {
            let mut v: Vec<&Animal> = Vec::new();
            v.push(&dog);
            v.push(&cat);
            for animal in v.iter() {
                println!("{}", animal.make_sound());
            }
        }
        let pete_dog: Dog = dog;
        println!("{}", pete_dog.make_sound());
    }
    

    【讨论】:

    • 谢谢。这是我缺少的线索。在较新的 Rust 版本中,我认为需要插入一个 dyn,例如 Vec&lt;&amp;dyn Animal&gt;
    猜你喜欢
    • 1970-01-01
    • 2020-11-08
    • 1970-01-01
    • 2016-08-27
    • 1970-01-01
    • 1970-01-01
    • 2015-06-30
    相关资源
    最近更新 更多