diff --git a/training-slides/src/dynamic-dispatch.md b/training-slides/src/dynamic-dispatch.md index 0f5cf75..f0566c5 100644 --- a/training-slides/src/dynamic-dispatch.md +++ b/training-slides/src/dynamic-dispatch.md @@ -48,25 +48,63 @@ impl Operation { ## Recommendation -Try to minimize repeated matches on the Enum, if not strictly necessary. +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 -References or raw pointers on traits, also boxes, describe so-called *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: -Trait objects are a pair of pointers to a virtual function table and the data. +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 object-safe traits (abbreviated) -- Object-safe traits are *not* allowed to require `Self: Sized` -- All methods are object-safe - * They have no type parameters - * They don't use `Self` +- 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/reference/items/traits.html#object-safety) for details. + +## Performance + +There is a small cost for jumping via the vtable, but it's cheaper than an enum match. + +See ## Trait Objects and Closures @@ -80,29 +118,27 @@ fn factory() -> Box i32> { } ``` -## Further properties +## Is this a reference to a String? -- As trait objects know about their exact type at runtime, they support downcasts through the `Any` trait. +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 [] -use std::fmt::Debug; -use std::any::Any; - -// Logger function for any type that implements Debug. -fn log(value: &T) { - let value_any = value as &dyn Any; - match value_any.downcast_ref::() { - Some(string) => { - println!("String ({}): {}", string.len(), string); - } - None => { - println!("Not a String: {:?}", value); - } +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() { - log(&"Some message".to_string()); - log(&1) + print_if_string(&0); + print_if_string(&String::from("cookie monster")); } ``` + +Note: + +Be sure to check the documentation because `Any` has some important restrictions.