HiveBrain v1.2.0
Get Started
← Back to all entries
patternrustModerate

Vector of objects belonging to a trait

Submitted by: @import:stackoverflow-api··
0
Viewed 0 times
objectsvectortraitbelonging

Problem

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 = 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 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-time


Note 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-time
let 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.