Vector of objects belonging to a trait

匿名 (未验证) 提交于 2019-12-03 02:11:02

问题:

Consider the following code:

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

The compiler tells me that v is a vector of Animal when I try to push cat (type mismatch)

So, how can I make a vector of objects belonging to a trait and calls the corresponding trait method on each element?

回答1:

Vec<Animal> is not legal, but the compiler can't tell you that because the type mismatch somehow hides it. If we remove the calls to push, the compiler gives us the following error:

<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();                   ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 

The reason why that's not legal is that a Vec<T> stores many T objects consecutively in memory. However, Animal is a trait, and traits have no size (a Cat and a Dog are not guaranteed to have the same size).

To solve this problem, we need to store something that has a size in the Vec. The most straightforward solution is to wrap the values in a Box, i.e. Vec<Box<Animal>>. Box<T> has a fixed size (a "fat pointer" if T is a trait, a simple pointer otherwise).

Here's a working 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());     } } 


回答2:

You may use a reference trait object &Animal to borrow the elements and store these trait objects in a Vec. You can then enumerate it and use the trait's interface.

Altering the Vec's generic type by adding a & in front of the trait will work:

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

This is great if you may want the original variable to keep ownership and reuse it later.

Keep in mind with the scenario above, you can't transfer ownership of dog or cat because the Vec has borrowed these concrete instances at the same scope.

Introducing a new scope can help handle that particular situation:

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


标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!