In this lesson, you will learn how to choose between the blazing speed of static dispatch and the runtime flexibility of dynamic dispatch.
When you use a trait as a generic constraint (e.g., <T: Trait>), Rust uses static dispatch. During compilation, Rust performs a process called monomorphization: it generates a unique version of your function for every specific type you use. Because the compiler knows exactly which type is being used, it can "inline" the function calls, leading to zero-overhead performance.
However, sometimes you want to store a collection of different types that all implement the same trait, such as Vec<Box<dyn Speak>>. This is dynamic dispatch. Instead of creating specialized code for every type, Rust uses a "vtable" (virtual method table)—a hidden pointer that looks up the correct method to run at runtime.
Choosing Your Tool
- Static Dispatch (
<T: Trait>): Use this by default. It is faster because the compiler knows the exact code to run, allowing for aggressive optimizations. It only breaks down if you need a collection of different types or if the compiled binary size becomes too bloated from excessive monomorphization. - Dynamic Dispatch (
dyn Trait): Use this when you need flexibility. It allows your program to handle types that weren't even known when you wrote the code, like a plugin system where users provide their own implementations. The trade-off is a tiny bit of runtime overhead because the program must perform a lookup to find the method address.
Consider this comparison:
// Static Dispatch: Fast, generates specific code for each T
fn run_static<T: Speak>(item: T) { item.speak(); }
// Dynamic Dispatch: Flexible, works with any type implementing Speak
fn run_dynamic(item: &dyn Speak) { item.speak(); }
Static dispatch turns your traits into concrete, optimized machine code, while dynamic dispatch trades a small amount of speed for the ability to handle heterogeneous types through a pointer lookup.
Key Takeaway: Prefer static dispatch for maximum performance, and reach for dynamic dispatch only when you need the flexibility to handle multiple, different types through a single interface.