Day 4 · Concept 12
Trait objects (dyn)
Generics give compile-time polymorphism (one specialised function per type).
dyn Trait gives runtime polymorphism — a single function that dispatches
through a vtable. Slower per call; smaller code; supports heterogeneous collections.
1 · Static vs dynamic dispatch
trait Animal { fn speak(&self); }
struct Dog;
struct Cat;
impl Animal for Dog { fn speak(&self) { println!("woof"); } }
impl Animal for Cat { fn speak(&self) { println!("meow"); } }
// Static — monomorphised, one version per type
fn announce_static<T: Animal>(a: T) { a.speak(); }
// Dynamic — one version, dispatch via vtable
fn announce_dyn(a: &dyn Animal) { a.speak(); }
fn main() {
announce_static(Dog);
announce_static(Cat);
announce_dyn(&Dog);
announce_dyn(&Cat);
// Heterogeneous collection — only works with dyn
let animals: Vec<Box<dyn Animal>> = vec![Box::new(Dog), Box::new(Cat)];
for a in &animals { a.speak(); }
}2 · When to use which
Static (generic <T: Trait>) | Dynamic (dyn Trait) |
|---|---|
| One specialised function per call site | One function, runtime dispatch |
| Zero overhead per call | ~1 indirect call per method invocation |
| Can't put different T in one collection | Can: Vec<Box<dyn Trait>> |
| Bigger binary (more instantiations) | Smaller binary |
3 · The object-safety rule
Not every trait can be a trait object. The rule: methods must take &self
or &mut self (not self); no generic methods; no
associated constants. The compiler enforces; the error message says "not object safe".
4 · When it clicks
- Generic
T: Traitby default;dyn Traitwhen you need heterogeneous collections. - You stop being surprised by "not object safe" — you know the rules.
- The cost-comparison (vtable lookup vs inlined) is in your mental model.
Found this useful?