属于特征的对象传染媒介

Jac*_*ang 52 polymorphism rust

请考虑以下代码:

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());
    }
}
Run Code Online (Sandbox Code Playgroud)

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

那么,我如何制作属于特征的对象向量并在每个元素上调用相应的特征方法呢?

Fra*_*gné 67

Vec<Animal>是不合法的,但编译器不能告诉你,因为类型不匹配以某种方式隐藏它.如果我们删除调用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();
                  ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Run Code Online (Sandbox Code Playgroud)

这不合法的原因是在内存中连续Vec<T>存储了许多T对象.然而,Animal是一种特性,并且特征没有大小(a Cat和a Dog不保证具有相同的大小).

要解决这个问题,我们需要存储具有大小的东西Vec.最直接的解决方案是将值包装在a中Box,即Vec<Box<Animal>>.Box<T>具有固定大小(如果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());
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 编写它的首选方法现在是`Box&lt;dyn Animal&gt;`。 (7认同)
  • @Boiethios 现在是必需的。 (7认同)
  • 是的,可以**确认**现在的语法应为 `Box&lt;dyn Animal&gt;`。 (3认同)
  • 这似乎正在使用特征对象,即动态调度。但是,[关于此主题的Rust编程语言书](https://doc.rust-lang.org/book/first-edition/trait-objects.html)说:“一个人总是可以拥有一个薄的静态分派包装函数。进行动态分配,反之亦然,这意味着静态调用更加灵活。” 在这种情况下,实际上有没有办法不使用特征对象而仅执行静态分派?我还遇到了一个用例,在该用例中,我想要一个向量来存储可能实现相同特征的不同类型的结构。 (2认同)
  • @JIXiang如果您事先知道要在`Vec`中放入什么具体类型,则可以改用`enum'(然后特性可能变得多余)。否则,您不能避免动态调度。请记住,动态调度并不是天生的邪恶。在某些情况下可以使用它! (2认同)

at5*_*321 26

现有的答案很好地解释了问题Vec<Animal>,但它们使用旧的语法,不再有效。

简而言之,向量需要包含特征对象,并且其类型应该是 (类似) Vec<Box<dyn Animal>>

在现代 Rust 中,dyn关键字用于指定特征对象。但我们不能只使用Vec<dyn Animal>,因为dyn Animal它没有大小Cat并且Dog可能有不同大小的字段)。向量只能包含固定大小的元素。这就是为什么在向量中我们应该存储某种指向实际结构的指针。结构体Box就是这样的一个选项,它是一种本身具有固定大小的智能指针。

让我们测试一下(在 64 位机器上):

use std::mem::size_of;
println!("size Cat = {}", size_of::<Cat>());  // 0 bytes (the Cat struct has no fields)
println!("size Dog = {}", size_of::<Dog>());  // 0 bytes (the Dog struct has no fields)
println!("size BoxCat = {}", size_of::<Box<Cat>>());  // 8 bytes (1 usize pntr)
println!("size BoxDyn = {}", size_of::<Box<dyn Animal>>());  // 16 bytes (2 usize pointers)
println!("{}", size_of::<dyn Animal>());  // Error: doesn't have a size known at compile-time
Run Code Online (Sandbox Code Playgroud)

请注意,如果Cat有字段,size_of::<Cat>()将会超过0,但size_of::<Box<Cat>>()size_of::<Box<dyn Animal>>()根本不会改变。

另请注意,Box<dyn Animal>实际上包含 2 个指针:

  • 指向实际结构实例数据的一个;
  • 一个用于vtable(这是因为;动态调度dyn需要它)。

现在以你的例子为例。要使其工作,您只需替换这三行:

let v: Vec<Animal> = Vec::new();
v.push(cat);
v.push(dog);    
Run Code Online (Sandbox Code Playgroud)

用这些:

let mut v: Vec<Box<dyn Animal>> = Vec::new();
v.push(Box::new(cat));
v.push(Box::new(dog));
Run Code Online (Sandbox Code Playgroud)

  • 非常明确的答案,这应该是公认的答案。 (5认同)

nat*_*ate 15

您可以使用引用特征对象&Animal借用元素并将这些特征对象存储在a中Vec.然后,您可以枚举它并使用特征的界面.

Vec通过&在特征前面添加a 来改变通用类型将起作用:

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());
}
Run Code Online (Sandbox Code Playgroud)

如果您希望原始变量保留所有权并在以后重复使用,那么这非常有用.

请记住上面的场景,您不能转移所有权,dog或者cat因为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());
}
Run Code Online (Sandbox Code Playgroud)

  • 谢谢。这对我来说是缺失的线索。在较新的 Rust 版本中,我认为需要插入一个 `dyn`,例如 `Vec&lt;&amp;dyn Animal&gt;`。 (3认同)