Trait Objects
Static vs Dynamic Dispatch
Section titled “Static vs Dynamic Dispatch”Rust gives you two ways to use trait polymorphism:
- Static dispatch (via generics): the compiler knows the exact type at compile time and generates specialized code for each one. Zero runtime overhead.
- Dynamic dispatch (via
dyn Trait): the concrete type is not known until runtime. The compiler stores a vtable — a table of function pointers — alongside the value so the right method can be found at runtime.
The dyn Trait Syntax
Section titled “The dyn Trait Syntax”When you write dyn Animal, you are asking Rust to use dynamic dispatch. Because the size of the concrete type is unknown at compile time, you must put a dyn Trait behind a pointer. The two most common forms are:
&dyn Trait— a borrowed trait object (no heap allocation)Box<dyn Trait>— an owned trait object stored on the heap
fn make_sound(animal: &dyn Animal) { println!("{} says: {}", animal.name(), animal.sound());}This function accepts any type that implements Animal — you do not need to know the concrete type at compile time.
Storing Mixed Types in a Collection
Section titled “Storing Mixed Types in a Collection”The most powerful use case for trait objects is storing values of different concrete types together in a Vec or other collection. With generics alone this is impossible — a Vec<T> can only hold one concrete type at a time.
let animals: Vec<Box<dyn Animal>> = vec![ Box::new(Dog), Box::new(Cat), Box::new(Dog),];Each element is a Box<dyn Animal> — an owned pointer to some type that implements Animal. The vtable stored alongside each value ensures the right sound() and name() implementations are called.
Trait Objects and Object Safety
Section titled “Trait Objects and Object Safety”Not every trait can be used as a dyn Trait. A trait is object-safe if:
- It has no methods that return
Self. - It has no generic type parameters on its methods.
The standard library traits Display, Debug, and Iterator are object-safe. Traits like Clone (which returns Self) are not.
When to Use Each
Section titled “When to Use Each”| Approach | Type known at compile time? | Runtime cost | Heterogeneous collection? |
|---|---|---|---|
Generic (impl Trait or T: Trait) | Yes | Zero | No |
Trait object (dyn Trait) | No | Small vtable indirection | Yes |
Use generics when you know the types at compile time and want maximum performance. Use trait objects when you need to work with a collection of mixed concrete types or when the type is determined by user input or configuration at runtime.
trait Animal { fn sound(&self) -> &str; fn name(&self) -> &str;}
struct Dog;struct Cat;
impl Animal for Dog { fn sound(&self) -> &str { "Woof" } fn name(&self) -> &str { "Dog" }}
impl Animal for Cat { fn sound(&self) -> &str { "Meow" } fn name(&self) -> &str { "Cat" }}
fn make_sound(animal: &dyn Animal) { println!("{} says: {}", animal.name(), animal.sound());}
fn main() { let animals: Vec<Box<dyn Animal>> = vec![ Box::new(Dog), Box::new(Cat), Box::new(Dog), ];
for animal in &animals { make_sound(animal.as_ref()); }}Compiling…