A strong answer
Rust gives you explicit control over the two ways machines execute polymorphic code: (1) Static Dispatch (fn run<T: Trait>(x: &T) or x: &impl Trait) uses Monomorphization: at compile time, the compiler stamps out a separate, specialized copy of run for each concrete type (run_FileHandler, run_MemHandler). Each call is a direct static jump that LLVM can inline completely (0 ns dispatch overhead!), at the cost of larger binary .text size. (2) Dynamic Dispatch (&dyn Trait or Box<dyn Trait>) erases the concrete type behind a 16-byte Fat Pointer: Word 0 (data_ptr) points to the struct instance, and Word 1 (vtable_ptr) points to a static read-only Virtual Method Table (vtable) containing (drop_in_place, size, align, method_1_ptr, method_2_ptr, ...)! Note how this differs from C++: in C++, every polymorphic object embeds a vptr header inside the object itself; in Rust, the struct has 0 bytes of vptr overhead, and the vtable_ptr is carried alongside data_ptr in the 16-byte &dyn Trait reference only when dynamic dispatch is requested!
A concrete struct in Rust never carries a hidden vtable pointer (size_of::<MyStruct>() is unchanged when implementing traits); converting &MyStruct (8 bytes) into &dyn Trait produces a 16-byte fat pointer (data_ptr, vtable_ptr) if and only if Trait is dyn-compatible (object-safe). For a trait to have a fixed-layout vtable array of function pointers at compile time, none of its vtable-dispatched methods can be generic (fn fooT(&self) would require infinitely many vtable slots for every possible T!) or return Self by value (whose stack size is unknown to the caller).
Monomorphized generics (<T: Trait>) enable inlining and loop vectorization (0 dispatch overhead), whereas dyn Trait costs 1 indirect function call through vtable_ptr (~2–4 ns, prevents inlining) while keeping binary code size compact and allowing heterogeneous collections (Vec<Box<dyn Trait>>). In production systems (Rust standard library std::fs::File::open<P: AsRef<Path>> (inner non-generic _open(p.as_ref())), tokio boxed futures Pin<Box<dyn Future>>, and Axum / Tower BoxCloneService middleware stacks.), engineers monitor these exact trade-offs to keep p99 latency predictable and prevent resource exhaustion under peak load.
What the interviewer is listening for
- Traces how static dispatch (Monomorphization) vs dynamic dispatch (
dyn Traitvtables) operates in memory step by step rather than giving a vague textbook definition. - States the core correctness invariant and explains why edge cases do not violate it.
- Quantifies time, space, and hardware costs (Static
<T: Trait>Monomorphization, Dynamic&dyn Trait/Box<dyn Trait>) with concrete numbers. - Identifies real-world failure modes (Using
Box<dyn Trait>inside a 100M-iteration numeric or iterator inner loop) and explains how production systems mitigate them.
Common mistakes
- Treating static dispatch (Monomorphization) vs dynamic dispatch (
dyn Traitvtables) as a black box without explaining the underlying memory or state transitions. - Ignoring worst-case tail latency or memory overhead (Writing a 300-line generic function
pub fn load<P: AsRef<Path>>(p: P)without delegating to a non-generic inner function). - Forgetting boundary conditions and error recovery (Returning
-> Box<dyn Iterator<Item = i32>>when only one concrete iterator chain is returned).