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.