static versus dynamic dispatch
When your code calls a method through a trait, there is a fundamental question: does the compiler know at build time exactly which function will run, or must the program figure it out while running? 'Dispatch' is just the word for resolving a call to a concrete function. Static dispatch decides at compile time; dynamic dispatch decides at run time. Rust lets you choose, and the choice is the difference between writing fn f<T: Trait>(x: T) (generic, static) and fn f(x: &dyn Trait) (trait object, dynamic).
With static dispatch, you use generics with a trait bound. For each concrete type you actually call the function with, the compiler stamps out a separate specialised copy of the function with the method calls resolved to the exact implementation — this is monomorphisation. The resulting call is a direct call to a known function, which the optimiser can inline, so a generic 'sort by Ord' becomes, after compilation, code as tight as if you had hand-written it for that one type. With dynamic dispatch, you use a trait object (dyn Trait). There is only one copy of the function, and at each call it reads the function pointer out of the vtable the value carries and calls through it — an indirect call that cannot be inlined and costs one extra memory load plus a harder-to-predict branch. The honest summary: static dispatch is faster per call and inlinable but generates a copy of the code per type (code bloat); dynamic dispatch is one shared copy and flexible (a heterogeneous Vec<Box<dyn Trait>>) but pays an indirection on every call.
Why this matters: this is one of the most practical performance choices in Rust, and the right answer is usually boring. Default to generics (static dispatch) for hot, type-known code where speed matters and the set of types is small; reach for dyn Trait (dynamic dispatch) when you genuinely need to mix types in one collection, when compile time or binary size from monomorphisation is hurting, or when the indirection cost is negligible next to the work the method does. The common misconception is that dynamic dispatch is 'slow' in a way that matters everywhere — usually the indirect call is lost in the noise, and the readability or flexibility of dyn is the better trade. Measure before assuming the cost matters.
trait Speak { fn say(&self) -> &str; } // STATIC dispatch: one specialised copy per concrete T, direct + inlinable. fn shout<T: Speak>(x: &T) { println!("{}!", x.say()); } // DYNAMIC dispatch: one copy, call goes through the vtable each time. fn shout_dyn(x: &dyn Speak) { println!("{}!", x.say()); } // Only dyn lets you store mixed types together: // let crowd: Vec<Box<dyn Speak>> = vec![ /* different types */ ];
Generics dispatch statically (fast, inlinable, code per type); dyn Trait dispatches dynamically (one copy, an indirect call per use).
Dynamic dispatch is not 'slow everywhere'. The indirect call is usually tiny next to the method's actual work, and dyn's flexibility often wins. Default to generics for hot, type-known paths; reach for dyn when you must mix types or monomorphisation bloats the binary — and measure.