notes

Log | Files | Refs | README

dynamic_dispatch.md (3786B)


      1 # Dynamic Dispatch
      2 
      3 **Dynamic dispatch** is when you have multiple types that something might be,
      4 but you don't know which one until runtime. So you **dynamically** figure out
      5 which one of the methods to call on the type. Usually doing this requires the
      6 use of **dyn** trait.
      7 
      8 Dynamic dispatch is when the concrete type implementing a trait is resolved at
      9 **runtime** rather than compile time. In Rust, this is done via trait objects
     10 (`dyn Trait`), and the compiler implements it using a
     11 [**vtable**](/compiler/vtable.md) — a table of function pointers generated for
     12 each concrete type.
     13 
     14 ## [Actors](/async_programming/actors.md) are dynamic dispatch
     15 
     16 When you use actors, you don't need dynamic dispatch, because actors provide
     17 dynamic dispatch on theri own.
     18 
     19 Storing something in an actor is an alternative to `Box<dyn Trait>`.
     20 
     21 ```rust
     22 struct MyActor<T: AsyncRead +  AsyncWrite> {
     23   receiver: mpsc::Receiver<ActorMessage>,
     24   connection: T,
     25 }
     26 ```
     27 
     28 Here, the generic T doesn't leak to the sender.
     29 
     30 ```rust
     31 #[derive(Clone)]
     32 pub struct MyActorHandle {
     33   sender: mpsc::Sender<ActorMessage>,
     34 }
     35 ```
     36 
     37 Here, T could be TcpStream or UnixStream depending on the connection. Remote
     38 connection uses TcpStream, whereas local connection uses UnixStream, for
     39 example.
     40 
     41 ## Static vs Dynamic Dispatch
     42 
     43 In **static dispatch**, the compiler monomorphizes generic functions — it
     44 generates a separate copy of the function for each concrete type used. Calls are
     45 resolved at compile time and can be inlined.
     46 
     47 ```rust
     48 fn area<T: Shape>(s: &T) -> f64 {
     49     s.area() // resolved at compile time
     50 }
     51 ```
     52 
     53 In **dynamic dispatch**, you use a trait object. The compiler doesn't know the
     54 concrete type at compile time, so it emits a vtable lookup at each call site.
     55 
     56 ```rust
     57 fn area(s: &dyn Shape) -> f64 {
     58     s.area() // resolved at runtime via vtable
     59 }
     60 ```
     61 
     62 ## How the Compiler Implements It
     63 
     64 A `dyn Trait` value is a **fat pointer** — two machine words:
     65 
     66 - A **data pointer** to the value itself
     67 - A **vtable pointer** to a static table of function pointers for the concrete
     68   type
     69 
     70 The vtable is emitted by the compiler for each `(ConcreteType, Trait)` pair.
     71 When you call a method on a `dyn Trait`, Rust loads the function pointer from
     72 the vtable and calls it indirectly. This means no inlining and a small overhead,
     73 but it enables **heterogeneous collections** and **type erasure**.
     74 
     75 ```
     76 Box<dyn Shape>
     77 ├── data ptr ──► [ Circle { radius: 3.0 } ]
     78 └── vtable ptr ─► [ drop, size, align, area, ... ]
     79 ```
     80 
     81 ## Object Safety
     82 
     83 Not every trait can be used as `dyn Trait`. A trait must be **object-safe** for
     84 this. The key rules are:
     85 
     86 - Methods must not return `Self`
     87 - Methods must not have generic type parameters
     88 - The trait must not require `Sized`
     89 
     90 The compiler enforces this — if a trait is not object-safe, using `dyn Trait` is
     91 a compile error.
     92 
     93 ## When to Use Dynamic Dispatch
     94 
     95 Prefer dynamic dispatch when:
     96 
     97 - You need a heterogeneous collection (e.g. `Vec<Box<dyn Plugin>>`)
     98 - You want to hide a concrete type behind an abstraction boundary (e.g. plugin
     99   systems, renderers, handlers)
    100 - Binary size matters more than the last bit of performance (monomorphization
    101   bloat is real)
    102 
    103 Prefer static dispatch when call-site performance and inlining are critical, or
    104 when the set of concrete types is small and known ahead of time.
    105 
    106 ## Relationship to Ownership
    107 
    108 Dynamic dispatch is orthogonal to both concurrency and memory safety. You can
    109 wrap a trait object in any ownership primitive:
    110 
    111 - `Box<dyn Trait>` — heap-allocated, single owner
    112 - `Rc<dyn Trait>` — reference-counted, single-threaded
    113 - `Arc<dyn Trait + Send + Sync>` — reference-counted, multi-threaded
    114 
    115 The ownership wrapper controls lifetime and thread safety; the vtable only
    116 controls how methods are dispatched.