Rust dynamic and static dispatch

Static Dispatch

What it is

  • The function implementation is chosen at compile time
  • Used by generics (T: Trait, impl Trait)

How it works

  • The compiler monomorphizes generic code
  • A separate, concrete version of the function is generated for each type
  • All types and sizes are known at compile time

Properties

  • No runtime dispatch cost
  • Function calls can be inlined
  • Enables aggressive optimizations
  • Larger binaries (more generated code)

example:

fn sum<I: Iterator<Item = i32>>(iter: I) -> i32 {
    iter.sum()
}

Use when

  • Performance matters
  • Code is in hot loops
  • Working with iterators or parallel code (e.g. Rayon)

Rayon: almost always static dispatch

Dynamic Dispatch

What it is

  • The function implementation is chosen at runtime
  • Used by trait objects (dyn Trait)

How it works

  • A single function body is compiled
  • Method calls go through a vtable
  • The concrete type is erased inside the function

Properties

  • Runtime cost (indirect function call)
  • No inlining of trait methods
  • Smaller binaries
  • Allows heterogeneous types at runtime
fn sum(iter: &mut dyn Iterator<Item = i32>) -> i32 {
    iter.sum()
}

Use when

  • The concrete type is not known at compile time
  • You need runtime polymorphism
  • You need to store or return different implementations via one interface

Important clarifications

  • dyn does not imply heap allocation
  • Heap allocation happens only with owning pointers like Box<dyn Trait>
  • &dyn Trait uses no heap
what is function call site: the code where the function is called.

how the compiler handle generics?
This is called monomorphization. For a function call, the compiler check the the call site, get each type fits into the generic type, and generate a version of the function automatically that only for that specific type.
Monomorphization = replacing generic code with concrete, type-specific code at compile time.
Generics let the programmer describe a family of functions parameterized by types, and the compiler expands that family into concrete, type-specific implementations wherever they’re needed.

Because it is compile time, so it allows:
exact type sizes
stack allocation
zero runtime overhead
full optimization

Leave a Reply

Your email address will not be published. Required fields are marked *