Under The Hood
backend rust Rust 1.x

Static Dispatch (Monomorphization) vs Dynamic Dispatch (dyn Trait VTable)

Last updated
Prerequisites:
Rust traits and generics
Understanding of dispatch and code generation
  • rust
  • generics
  • monomorphization
  • trait-objects
  • vtable
  • dynamic-dispatch

Read at your depth

The practical view

Generics with trait bounds use static dispatch: fn process<T: Handler>(h: T) — the compiler generates a specialized copy for each concrete type (monomorphization), and calls are direct and inlinable. Trait objects use dynamic dispatch: fn process(h: &dyn Handler) — the value is a fat pointer (data pointer + vtable pointer), and method calls go through the vtable at runtime. Static dispatch is faster (no indirection, inlining possible) but increases binary size; dynamic dispatch is flexible (heterogeneous collections, runtime type choice) with a small per-call cost. Choose generics for hot paths, dyn for collections of mixed types or runtime-selected implementations.

The same idea in other frameworks

react equivalent
N/A — Rust language feature.

Legacy vs modern

Generic static dispatch vs dyn Trait object

Generics specialize per type (direct calls, inlining); trait objects go through a vtable (flexible, one indirection per call).

before → after
dyn Trait in a hot loop
fn sum_all(items: &[&dyn Computable]) -> f64 { // ❌ OLD: vtable call per item
Generic monomorphized dispatch
fn sum_all<T: Computable>(items: &[T]) -> f64 { // ✅ NEW: direct calls

Interview gotchas

Context

This tests the engineering trade-off — interviewers want code-size/bloat and flexibility considerations, not just the dispatch-speed soundbite.

The mechanical answer

Monomorphization benefits: direct calls (inlinable), no per-call indirection, better constant propagation — and the specialized code can be optimized against the concrete type. Costs: code bloat — each concrete instantiation is a separate copy, so N types × M generic functions can blow up binary size and compile time; this is especially painful with many large generic functions. Trait objects benefit: one copy of the function (smaller binary for heterogeneous use), runtime flexibility (collections of mixed types, plugins, runtime-selected implementations), and stable compile times. Costs: vtable indirection per call (prevents inlining in most cases), fat pointers (2× memory per reference in collections), and the object-safety restriction (no generic methods, no Self-by-value returns). The choice is a real engineering trade-off: hot, type-fixed paths favor generics; polymorphic, mixed-type, or plugin-like boundaries favor dyn.

Trap

The naive answer is 'always use generics, they're faster'. Code bloat and compile-time cost are real; for a small number of instantiations the bloat is minor, but large generic-heavy codebases pay. Another trap: assuming trait objects can express anything generics can — object safety forbids generic methods and by-value Self returns, which is why Box<dyn Clone> doesn't work directly. Candidates who weigh binary size and object safety show real understanding.

Under The Hood — a multi-depth technical interview hub.

Third-party licenses Press ⌘ K to search.