What is the difference between static and dynamic dispatch in Rust?
Compare Rust's static dispatch (generics, monomorphization) with dynamic dispatch (dyn Trait, vtables): how each works, the trade-offs, and when to use each.
Expected Interview Answer
Static dispatch resolves which concrete method to call at compile time by monomorphizing generic code for each type, while dynamic dispatch resolves the call at runtime through a vtable behind a trait object like &dyn Trait or Box<dyn Trait>.
With static dispatch (generics or impl Trait), the compiler generates a specialized copy of the function per concrete type, enabling inlining and maximum speed at the cost of larger binaries. With dynamic dispatch, values of different types share one function that looks up the correct method through a pointer table at runtime — smaller code and heterogeneous collections, but an indirection that blocks inlining. You choose static when the type is known and hot, dynamic when you need to store mixed types together or shrink code size.
- Static dispatch enables inlining and peak runtime performance
- Dynamic dispatch allows heterogeneous collections of one trait
- Trait objects reduce code bloat from monomorphization
- Static keeps type information for aggressive optimization
- Dynamic keeps binaries smaller and compile units simpler
AI Mentor Explanation
Static dispatch is like a captain who has memorised each bowler's exact action and calls the delivery instantly, with no thinking at the crease. Dynamic dispatch is like consulting the match rulebook every ball to look up who bowls next — one shared lookup table handles any bowler, but the glance costs a beat of time each delivery.
Step-by-Step Explanation
Step 1
Write against a trait
Define behaviour in a trait so multiple concrete types can implement it.
Step 2
Choose generics for static
Use fn f<T: Trait>(x: T) or impl Trait; the compiler monomorphizes a copy per concrete type.
Step 3
Choose dyn for dynamic
Use &dyn Trait or Box<dyn Trait>; a fat pointer carries the data plus a vtable of method pointers.
Step 4
Understand the trade-off
Static inlines and is fastest but grows the binary; dynamic shares one code path and enables mixed-type collections.
Step 5
Pick per use case
Prefer static in hot, type-known paths; reach for dynamic to store heterogeneous types or cut code bloat.
What Interviewer Expects
- Knowing static dispatch means monomorphization at compile time
- Knowing dynamic dispatch uses a vtable via dyn Trait at runtime
- Recognising trait objects are fat pointers (data + vtable)
- Articulating the speed vs binary-size / flexibility trade-off
- Giving a case where each choice is the right one
Common Mistakes
- Thinking generics have runtime dispatch cost like virtual calls
- Believing dyn Trait can be stored on the stack with a known size
- Assuming dynamic dispatch is always slow enough to matter
- Forgetting that monomorphization can bloat the binary
- Confusing impl Trait in argument position with dyn Trait
Best Answer (HR Friendly)
“Static dispatch means the compiler decides exactly which code to run ahead of time, so it is very fast, while dynamic dispatch decides at the moment the program runs by looking the method up in a table. Static is best when speed matters and the type is known; dynamic is handy when you want to mix different types together in one place.”
Code Example
trait Speak {
fn say(&self) -> &str;
}
struct Dog;
struct Cat;
impl Speak for Dog { fn say(&self) -> &str { "woof" } }
impl Speak for Cat { fn say(&self) -> &str { "meow" } }
// Static dispatch: monomorphized per T, inlinable, resolved at compile time.
fn static_call<T: Speak>(s: &T) -> &str {
s.say()
}
// Dynamic dispatch: one function, method resolved via vtable at runtime.
fn dynamic_call(s: &dyn Speak) -> &str {
s.say()
}
fn main() {
println!("{}", static_call(&Dog));
// Heterogeneous collection needs trait objects (dynamic dispatch):
let animals: Vec<Box<dyn Speak>> = vec![Box::new(Dog), Box::new(Cat)];
for a in &animals {
println!("{}", dynamic_call(a.as_ref()));
}
}Follow-up Questions
- What is a vtable and what does it store?
- Why can't you put different concrete types in a Vec<T> without dyn?
- How does monomorphization affect binary size and compile time?
- What is object safety and why does it restrict dyn Trait?
- When is impl Trait static and when is dyn Trait needed?
MCQ Practice
1. How does Rust implement static dispatch for generic functions?
The compiler generates a distinct specialized version of the function for each concrete type, resolving calls at compile time.
2. What backs a dyn Trait call at runtime?
A trait object is a fat pointer to the data plus a vtable; calls index into the vtable to find the concrete method.
3. Which is a genuine advantage of dynamic dispatch?
Trait objects let you keep values of different concrete types together in one collection, which static dispatch cannot do.
Flash Cards
How is static dispatch resolved? — At compile time via monomorphization — one specialized copy per concrete type.
How is dynamic dispatch resolved? — At runtime through a vtable behind a trait object (&dyn Trait / Box<dyn Trait>).
What is a trait object's representation? — A fat pointer: one pointer to the data and one to the vtable of method pointers.
Main cost of static dispatch? — Code bloat — the binary grows with a copy per instantiated type.
When do you need dynamic dispatch? — To store different concrete types together in one collection or to reduce binary size.