C.W.K.
Stream
Lesson 05 of 07 · published

dyn vs impl: Dynamic and Static Dispatch

~12 min · traits, dyn, trait-objects, dispatch

Level 0Rust Curious
0 XP0/80 lessons0/19 achievements
0/100 XP to next level100 XP to go0% complete

You've used generics for static dispatch: the compiler stamps out a specialized copy per type. Sometimes you need the opposite — one collection holding many different types behind a shared trait. That's dynamic dispatch, via dyn.

Static dispatch (generics)

A generic fn render<T: Draw>(item: &T) is resolved at compile time: each concrete T gets its own compiled version, and the method call is direct — as fast as any function call. The cost is code size (one copy per type) and the constraint that any single call site works with exactly one type.

Dynamic dispatch (dyn Trait)

A &dyn Draw or Box<dyn Draw> is a trait object: a pointer to some value plus a pointer to a table of its trait methods (a vtable). The concrete type is erased; the method is looked up at runtime. This is what lets a Vec<Box<dyn Draw>> hold circles, squares, and text all at once — impossible with a generic, which fixes one type per use.

Generics for one-type-per-call speed; dyn for many-types-in-one-collection flexibility. Reach for generics by default — they're zero-cost. Reach for dyn Trait when you genuinely need a heterogeneous collection or to store differently-typed values behind one interface, and accept the small vtable indirection.

Why Box is usually involved

A trait object has no known size — a circle and a long string differ in bytes — so you can't store one directly on the stack. You put it behind a pointer: Box<dyn Trait> (owned, on the heap) or &dyn Trait (borrowed). The Box gives the trait object a fixed-size handle, which is exactly what the Smart Pointers track is about.

Code

A heterogeneous collection via trait objects·rust
trait Draw {
    fn draw(&self) -> String;
}

struct Circle;
struct Square;
impl Draw for Circle { fn draw(&self) -> String { "O".into() } }
impl Draw for Square { fn draw(&self) -> String { "[]".into() } }

fn main() {
    // Dynamic dispatch: one Vec holds DIFFERENT concrete types
    let shapes: Vec<Box<dyn Draw>> = vec![Box::new(Circle), Box::new(Square)];
    for s in &shapes {
        println!("{}", s.draw()); // method resolved at runtime via the vtable
    }
}

External links

Exercise

Define a Shape trait with an area(&self) -> f64. Implement it for Circle and Rectangle. Build a Vec<Box<dyn Shape>> containing both and sum their areas in a loop. Then try to write the same with a generic Vec<T> and explain why it can't hold both types.
Hint
A Vec<T> fixes one concrete T, so it can't hold a Circle and a Rectangle together. Vec<Box<dyn Shape>> erases the concrete type behind a trait object, allowing the mix — at the cost of runtime dispatch.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue
💛 by Ttoriwarm

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.