On this page
Trait Objects and Dynamic Dispatch
Generics generate a copy of code for each type at compile time (static dispatch, zero runtime overhead), while
dyn Traitlooks up function pointers via a vtable at runtime (dynamic dispatch, one code serving multiple types). Object safety determines which traits can become trait objects, andBox<dyn>vs enum is the choice between dynamic polymorphism and algebraic data types.
Two Dispatch Mechanisms: Compile-time vs Runtime
// Static dispatch: Generics. Generates independent code for each T at compile time, zero runtime overhead
// Dynamic dispatch: Trait object. Uses pointer + vtable, with indirect call overhead
When to use which? If you know all possible types at compile time → static dispatch. If types are determined at runtime, or you need a heterogeneous collection (storing different concrete types in the same container) → dynamic dispatch. A classic heterogeneous scenario:
let items: = vec!;
for item in &items
Internal Implementation of vtable
&dyn Display at runtime consists of two pointers (fat pointer, 16 bytes on 64-bit):
ptr_to_data: Points to the actual data on the heap (e.g., i32 or String)
ptr_to_vtable: Points to a static vtable structure
vtable for i32 as Display:
[0]: drop_in_place<i32> — How to deallocate
[1]: size_of::<i32> (4) — Data size
[2]: align_of::<i32> (4) — Alignment
[3]: <i32 as Display>::fmt — Pointer to the specific Display::fmt function
When calling x.fmt(f): read the vtable → fetch the 3rd entry → indirect jump. This adds one extra pointer dereference compared to a static call (usually prefetched by the CPU branch predictor, but still an additional instruction).
Object Safety: Not All Traits Can Be dyn
Trait objects require "object safety"—because the compiler must be able to generate a vtable. Traits that do not satisfy this include:
// let x: &dyn NotObjectSafe = ...; // ERROR: the trait is not object safe
Clone is not object safe—fn clone(&self) -> Self requires the return type to be known at the call site at compile time. Therefore, you cannot use Box<dyn Clone>. However, you can use Box<dyn Any> for type erasure and downcasting.
Trade-offs: dyn vs enum
// enum: Closed set at compile time, no virtual dispatch
// dyn: Open set (extensible), virtual dispatch (one indirect jump per call)
let shapes: = vec!;
Enums are suitable when "I know all possible variants" and the number of variants is small and rarely changes. dyn is suitable when "downstream can add more implementations" or when there are too many variants (scaling to dozens or hundreds of enum variants is impractical).
References
- Rust Book: Chapter 17.2
- Rust Reference: vtable layout, object safety
- Rustonomicon: dyn Trait internals
Keywords: dyn Trait, vtable, object safety, Box