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