Trait Objects

Trait objects allow for values of different types, for instance in a collection:

struct Dog {
    name: String,
    age: i8,
}
struct Cat {
    lives: i8,
}

trait Pet {
    fn talk(&self) -> String;
}

impl Pet for Dog {
    fn talk(&self) -> String {
        format!("Woof, my name is {}!", self.name)
    }
}

impl Pet for Cat {
    fn talk(&self) -> String {
        String::from("Miau!")
    }
}

fn main() {
    let pets: Vec<Box<dyn Pet>> = vec![
        Box::new(Cat { lives: 9 }),
        Box::new(Dog { name: String::from("Fido"), age: 5 }),
    ];
    for pet in pets {
        println!("Hello, who are you? {}", pet.talk());
    }
}

Memory layout after allocating pets:

StackHeap
.- - - - - - - - - - - - - -. .- - - - - - - - - - - - - - - - - - - - - - -. : : : : : "pets: Vec" : : "data: Cat" +----+----+----+----+ : : +-----------+-------+ : : +-------+-------+ | F | i | d | o | : : | ptr | o---+---+--. : | lives | 9 | +----+----+----+----+ : : | len | 2 | : | : +-------+-------+ ^ : : | capacity | 2 | : | : ^ | : : +-----------+-------+ : | : | '-------. : : : | : | data:"Dog"| : : : | : | +-------+--|-------+ : `- - - - - - - - - - - - - -' | : +---|-+-----+ | name | o, 4, 4 | : `--+-->| o o | o o-|----->| age | 5 | : : +-|---+-|---+ +-------+----------+ : : | | : `- - -| - - |- - - - - - - - - - - - - - - - -' | | | | "Program text" .- - -| - - |- - - - - - - - - - - - - - - - -. : | | vtable : : | | +----------------------+ : : | `----->| "::talk" | : : | +----------------------+ : : | vtable : : | +----------------------+ : : '----------->| "::talk" | : : +----------------------+ : : : '- - - - - - - - - - - - - - - - - - - - - - -'
This slide should take about 10 minutes.
  • Types that implement a given trait may be of different sizes. This makes it impossible to have things like Vec<dyn Pet> in the example above.
  • dyn Pet is a way to tell the compiler about a dynamically sized type that implements Pet.
  • In the example, pets is allocated on the stack and the vector data is on the heap. The two vector elements are fat pointers:
    • A fat pointer is a double-width pointer. It has two components: a pointer to the actual object and a pointer to the virtual method table (vtable) for the Pet implementation of that particular object.
    • The data for the Dog named Fido is the name and age fields. The Cat has a lives field.
  • Compare these outputs in the above example:
    println!("{} {}", std::mem::size_of::<Dog>(), std::mem::size_of::<Cat>());
    println!("{} {}", std::mem::size_of::<&Dog>(), std::mem::size_of::<&Cat>());
    println!("{}", std::mem::size_of::<&dyn Pet>());
    println!("{}", std::mem::size_of::<Box<dyn Pet>>());