Search code examples
polymorphismrust

Vector of objects belonging to a trait


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?


Solution

  • The existing answers explain the problem with Vec<Animal> 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<Box<dyn Animal>>.

    In modern Rust, the dyn keyword is used to specify a trait object. But we cannot use just Vec<dyn Animal>, 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::<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
    

    Note that if Cat had fields, size_of::<Cat>() would have been more than 0, but size_of::<Box<Cat>>() and size_of::<Box<dyn Animal>>() wouldn't change at all.

    Also note that Box<dyn Animal> 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<Animal> = Vec::new();
    v.push(cat);
    v.push(dog);    
    

    with these:

    let mut v: Vec<Box<dyn Animal>> = Vec::new();
    v.push(Box::new(cat));
    v.push(Box::new(dog));