# Dynamic Dispatch --- Sometimes, we want to take the decision of which implementation to use at runtime instead of letting the compiler monomorphize the code. There's two approaches. --- ## Dispatch through Enums If the number of possible choices is limited, an Enum can be used: ```rust [] enum Operation { Get, Set(String), Count } fn execute(op: Operation) { match op { Operation::Get => { } Operation::Set(s) => { } Operation::Count => { } } } ``` --- ## Alternative Form ```rust [] enum Operation { Get, Set(String), Count } impl Operation { fn execute(&self) { match &self { &Operation::Get => { } &Operation::Set(s) => { } &Operation::Count => { } } } } ``` --- ## Recommendation For best performance, try to minimize repeated matches on the `enum`. See
Note: It takes multiple instructions to extract the tag from the enum and then jump to the appropriate block of code based on the value of that tag. If you use the Trait Objects we describe later, the *kind* of thing is encoded in the pointer to the dynamic dispatch table (or v-table) and so the CPU can just do two jumps instead of 'if this is 0, do X, else if this is a 1, do Y, else ...'. --- ## Trait Objects We can make references which do not know the type of the value but instead only know one particular trait that the value implements. This is a *trait object*. Internally, trait objects are a pair of pointers - one to a vtable and one the value itself. Note: The term *vtable* is short for virtual dispatch table, and it's basically a struct full of function pointers that is auto-generated by the compiler. --- ## Usage ```rust fn print(thing: &dyn std::fmt::Debug) { // I can call `std::fmt::Debug` methods on `thing` println!("{:?}", thing); // But I don't know what the *actual* type is } fn main() { print(&String::from("hello")); print(&123); } ``` --- ## Limitations - You can only use one trait per object - Plus auto traits, like `Send` and `Sync` - This trait must fulfill certain conditions --- ## Rules for dyn-compatible traits (abbreviated) - Must not have `Self: Sized` - No associated constants or GATs - All methods must: - Have no type parameters - Not use `Self`, only `&self` etc - Not return `impl Trait` See [the docs](https://doc.rust-lang.org/nightly/reference/items/traits.html#dyn-compatibility) for details. Note that these used to be called "object safety" rules before 1.83. --- ## Performance There is a small cost for jumping via the vtable, but it's cheaper than an enum match. See
--- ## Trait Objects and Closures Closure traits are dyn-compatible. ```rust [] fn factory() -> Box
i32> { let num = 5; Box::new(move |x| x + num) } ``` --- ## Is this a reference to a String? Any type that is `'static + Sized` implements [`std::any::Any`](https://doc.rust-lang.org/stable/std/any/index.html). We can use this to ask "is this reference actually a reference to *this specific type*?" ```rust [] fn print_if_string(value: &dyn std::any::Any) { if let Some(s) = value.downcast_ref::
() { println!("It's a string({}): '{}'", s.len(), s); } else { println!("Not a string..."); } } fn main() { print_if_string(&0); print_if_string(&String::from("cookie monster")); } ``` Note: Be sure to check the documentation because `Any` has some important restrictions.