|
| 1 | +--- |
| 2 | +minutes: 5 |
| 3 | +--- |
| 4 | + |
| 5 | +# `dyn Trait` |
| 6 | + |
| 7 | +In addition to using traits for static dispatch via generics, Rust also supports |
| 8 | +using them for type-erased, dynamic dispatch via trait objects: |
| 9 | + |
| 10 | +```rust,editable |
| 11 | +struct Dog { |
| 12 | + name: String, |
| 13 | + age: i8, |
| 14 | +} |
| 15 | +struct Cat { |
| 16 | + lives: i8, |
| 17 | +} |
| 18 | +
|
| 19 | +trait Pet { |
| 20 | + fn talk(&self) -> String; |
| 21 | +} |
| 22 | +
|
| 23 | +impl Pet for Dog { |
| 24 | + fn talk(&self) -> String { |
| 25 | + format!("Woof, my name is {}!", self.name) |
| 26 | + } |
| 27 | +} |
| 28 | +
|
| 29 | +impl Pet for Cat { |
| 30 | + fn talk(&self) -> String { |
| 31 | + String::from("Miau!") |
| 32 | + } |
| 33 | +} |
| 34 | +
|
| 35 | +// Uses generics and static dispatch. |
| 36 | +fn generic(pet: &impl Pet) { |
| 37 | + println!("Hello, who are you? {}", pet.talk()); |
| 38 | +} |
| 39 | +
|
| 40 | +// Uses type-erasure and dynamic dispatch. |
| 41 | +fn dynamic(pet: &dyn Pet) { |
| 42 | + println!("Hello, who are you? {}", pet.talk()); |
| 43 | +} |
| 44 | +
|
| 45 | +fn main() { |
| 46 | + let cat = Cat { lives: 9 }; |
| 47 | + let dog = Dog { name: String::from("Fido"), age: 5 }; |
| 48 | +
|
| 49 | + generic(&cat); |
| 50 | + generic(&dog); |
| 51 | +
|
| 52 | + dynamic(&cat); |
| 53 | + dynamic(&dog); |
| 54 | +} |
| 55 | +``` |
| 56 | + |
| 57 | +<details> |
| 58 | + |
| 59 | +- Generics, including `impl Trait`, use monomorphization to create a specialized |
| 60 | + instance of the function for each different type that the generic is |
| 61 | + instantiated with. This means that calling a trait method from within a |
| 62 | + generic function still uses static dispatch, as the compiler has full type |
| 63 | + information and can resolve which type's trait implementation to use. |
| 64 | + |
| 65 | +- When using `dyn Trait`, it instead uses dynamic dispatch through a |
| 66 | + [virtual method table][vtable] (vtable). This means that there's a single |
| 67 | + version of `fn dynamic` that is used regardless of what type of `Pet` is |
| 68 | + passed in. |
| 69 | + |
| 70 | +- When using `dyn Trait`, the trait object needs to be behind some kind of |
| 71 | + indirection. In this case it's a reference, though smart pointer types like |
| 72 | + `Box` can also be used (this will be demonstrated on day 3). |
| 73 | + |
| 74 | +- At runtime, a `&dyn Pet` is represented as a "fat pointer", i.e. a pair of two |
| 75 | + pointers: One pointer points to the concrete object that implements `Pet`, and |
| 76 | + the other points to the vtable for the trait implementation for that type. |
| 77 | + When calling the `talk` method on `&dyn Pet` the compiler looks up the |
| 78 | + function pointer for `talk` in the vtable and then invokes the function, |
| 79 | + passing the pointer to the `Dog` or `Cat` into that function. The compiler |
| 80 | + doesn't need to know the concrete type of the `Pet` in order to do this. |
| 81 | + |
| 82 | +- A `dyn Trait` is considered to be "type-erased", because we no longer have |
| 83 | + compile-time knowledge of what the concrete type is. |
| 84 | + |
| 85 | +[vtable]: https://en.wikipedia.org/wiki/Virtual_method_table |
| 86 | + |
| 87 | +</details> |
0 commit comments