Static dispatch with impl Trait versus dynamic dispatch with Box<dyn Trait>
Tests monomorphization versus vtables. Note: static dispatch monomorphizes for zero-cost abstraction but bloats code; dynamic dispatch uses vtables for smaller binaries but adds indirection.
WHAT THIS TESTS: This question probes your understanding of Rust's two primary polymorphism mechanisms and whether you can reason about compile-time versus runtime costs. The interviewer wants to see that you know when monomorphization happens, how vtables work, and what object safety means. At the senior level, they also care if you can discuss binary size, cache effects, and compile-time trade-offs rather than repeating textbook definitions.
A GOOD ANSWER COVERS: A strong response starts by stating that impl Trait in an argument position is syntactic sugar for a generic parameter, which causes the compiler to monomorphize the function for every concrete type used at the call site. This produces a specialized copy of the code, enabling inlining and eliminating indirect jumps, which is why it is called a zero-cost abstraction. Next, it explains that Box<dyn Trait> is a trait object consisting of a fat pointer with two components: a data pointer and a vtable pointer. Dynamic dispatch looks up the correct method in the vtable at runtime, which adds a pointer chase and generally prevents inlining. Then, the answer contrasts the trade-offs: static dispatch can increase binary size and compile times because code is duplicated per type, while dynamic dispatch produces smaller binaries and faster compile times at the cost of runtime indirection. Finally, a senior candidate mentions object safety: traits with generic methods or methods that return Self by value cannot be made into trait objects, which often forces the choice in practice.
COMMON WRONG ANSWERS: A major red flag is claiming that impl Trait is always faster and should be used everywhere. Another weak pattern is describing Box<dyn Trait> as just a pointer without mentioning the vtable or fat pointer structure. Some candidates confuse impl Trait in argument position with impl Trait in return position, where the latter hides the concrete type but still uses static dispatch via an opaque type. Failing to mention object safety or binary bloat also signals shallow familiarity.
LIKELY FOLLOW-UPS: The interviewer might ask how async traits affect this choice, since async fn in traits historically required dynamic dispatch or the async_trait macro before native support stabilized. They could also ask about the size of a Box<dyn Trait> versus a generic parameter, or how dyn Trait interacts with lifetimes. Another follow-up is when you would choose an enum over a trait object for polymorphism.
ONE CONCRETE EXAMPLE: Imagine a logging framework with a write method. Using fn log(writer: impl Write), the compiler generates a separate log function for File, TcpStream, and Vec<u8>, each fully optimized and inlined. Using fn log(writer: Box<dyn Write>), the framework stores heterogeneous writers in a vector and dispatches through a vtable at runtime, keeping the binary smaller but adding a vtable lookup on every write call.
Read the original → doc.rust-lang.org
Get five bites like this every day.
Tezvyn delivers a daily feed of 60-second tech bites with quizzes to lock in what you learn.