12 / 20 · Day 4
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

rust src/main.rs · the two flavours
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 siteOne function, runtime dispatch
Zero overhead per call~1 indirect call per method invocation
Can't put different T in one collectionCan: 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: Trait by default; dyn Trait when 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?