patternrustModerate
Vector of objects belonging to a trait
Viewed 0 times
objectsvectortraitbelonging
Problem
Consider the following code:
The compiler tells me that
So, how can I make a vector of objects belonging to a trait and calls the corresponding trait method on each element?
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 = 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?
Solution
The existing answers explain the problem with
In short, the vector needs to contain trait objects and its type should be (something like)
In modern Rust, the
Let's test this (on a 64-bit machine):
Note that if
Also note that
Now to your example. To make it work, you just need to replace these three lines:
with these:
Vec well, but they use older syntax, which is not valid anymore.In short, the vector needs to contain trait objects and its type should be (something like)
Vec>.In modern Rust, the
dyn keyword is used to specify a trait object. But we cannot use just Vec, because dyn Animal is not sized (Cat and Dog could pottentially have fields of different size). Vectors can only contain elements of a fixed size. So that's why in the vector we should rather store some sort of pointers to the actual structs. The Box struct is one such option, a kind of a smart pointer that has a fixed size in itself.Let's test this (on a 64-bit machine):
use std::mem::size_of;
println!("size Cat = {}", size_of::()); // 0 bytes (the Cat struct has no fields)
println!("size Dog = {}", size_of::()); // 0 bytes (the Dog struct has no fields)
println!("size BoxCat = {}", size_of::>()); // 8 bytes (1 usize pntr)
println!("size BoxDyn = {}", size_of::>()); // 16 bytes (2 usize pointers)
println!("{}", size_of::()); // Error: doesn't have a size known at compile-timeNote that if
Cat had fields, size_of::() would have been more than 0, but size_of::>() and size_of::>() wouldn't change at all.Also note that
Box actually contains 2 pointers:- one that points to the actual struct instance data;
- one for the vtable (that's because of
dyn; it's needed for dynamic dispatching).
Now to your example. To make it work, you just need to replace these three lines:
let v: Vec = Vec::new();
v.push(cat);
v.push(dog);with these:
let mut v: Vec> = Vec::new();
v.push(Box::new(cat));
v.push(Box::new(dog));Code Snippets
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-timelet v: Vec<Animal> = Vec::new();
v.push(cat);
v.push(dog);let mut v: Vec<Box<dyn Animal>> = Vec::new();
v.push(Box::new(cat));
v.push(Box::new(dog));Context
Stack Overflow Q#25818082, score: 44
Revisions (0)
No revisions yet.