mirror of
https://github.com/google/comprehensive-rust.git
synced 2024-12-01 02:42:20 +02:00
Add a slide introducing dyn Trait
in Generics (#2108)
I've been thinking it'd be simpler to introduce `dyn Trait` via `&dyn Trait` rather than waiting for the smart pointers section and `Box<dyn Trait>`. This PR adds a slide to the Generics section that introduces `&dyn Trait` and compares it to `&impl Trait`, juxtaposing monomorphization and static dispatch against type-erasure and dynamic dispatch. I've then updated the existing trait object slide to call back to the earlier introduction, and call out that using `Box<dyn Trait>` gives you an owned trait object rather than a borrowed one.
This commit is contained in:
parent
06264e8cc7
commit
59bf3bdcfb
@ -100,6 +100,7 @@
|
||||
- [Generic Traits](generics/generic-traits.md)
|
||||
- [Trait Bounds](generics/trait-bounds.md)
|
||||
- [`impl Trait`](generics/impl-trait.md)
|
||||
- [`dyn Trait`](generics/dyn-trait.md)
|
||||
- [Exercise: Generic `min`](generics/exercise.md)
|
||||
- [Solution](generics/solution.md)
|
||||
- [Standard Library Types](std-types.md)
|
||||
@ -141,7 +142,7 @@
|
||||
- [Smart Pointers](smart-pointers.md)
|
||||
- [`Box<T>`](smart-pointers/box.md)
|
||||
- [`Rc`](smart-pointers/rc.md)
|
||||
- [Trait Objects](smart-pointers/trait-objects.md)
|
||||
- [Owned Trait Objects](smart-pointers/trait-objects.md)
|
||||
- [Exercise: Binary Tree](smart-pointers/exercise.md)
|
||||
- [Solution](smart-pointers/solution.md)
|
||||
|
||||
|
87
src/generics/dyn-trait.md
Normal file
87
src/generics/dyn-trait.md
Normal file
@ -0,0 +1,87 @@
|
||||
---
|
||||
minutes: 5
|
||||
---
|
||||
|
||||
# `dyn Trait`
|
||||
|
||||
In addition to using traits for static dispatch via generics, Rust also supports
|
||||
using them for type-erased, dynamic dispatch via trait objects:
|
||||
|
||||
```rust,editable
|
||||
struct Dog {
|
||||
name: String,
|
||||
age: i8,
|
||||
}
|
||||
struct Cat {
|
||||
lives: i8,
|
||||
}
|
||||
|
||||
trait Pet {
|
||||
fn talk(&self) -> String;
|
||||
}
|
||||
|
||||
impl Pet for Dog {
|
||||
fn talk(&self) -> String {
|
||||
format!("Woof, my name is {}!", self.name)
|
||||
}
|
||||
}
|
||||
|
||||
impl Pet for Cat {
|
||||
fn talk(&self) -> String {
|
||||
String::from("Miau!")
|
||||
}
|
||||
}
|
||||
|
||||
// Uses generics and static dispatch.
|
||||
fn generic(pet: &impl Pet) {
|
||||
println!("Hello, who are you? {}", pet.talk());
|
||||
}
|
||||
|
||||
// Uses type-erasure and dynamic dispatch.
|
||||
fn dynamic(pet: &dyn Pet) {
|
||||
println!("Hello, who are you? {}", pet.talk());
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let cat = Cat { lives: 9 };
|
||||
let dog = Dog { name: String::from("Fido"), age: 5 };
|
||||
|
||||
generic(&cat);
|
||||
generic(&dog);
|
||||
|
||||
dynamic(&cat);
|
||||
dynamic(&dog);
|
||||
}
|
||||
```
|
||||
|
||||
<details>
|
||||
|
||||
- Generics, including `impl Trait`, use monomorphization to create a specialized
|
||||
instance of the function for each different type that the generic is
|
||||
instantiated with. This means that calling a trait method from within a
|
||||
generic function still uses static dispatch, as the compiler has full type
|
||||
information and can resolve which type's trait implementation to use.
|
||||
|
||||
- When using `dyn Trait`, it instead uses dynamic dispatch through a
|
||||
[virtual method table][vtable] (vtable). This means that there's a single
|
||||
version of `fn dynamic` that is used regardless of what type of `Pet` is
|
||||
passed in.
|
||||
|
||||
- When using `dyn Trait`, the trait object needs to be behind some kind of
|
||||
indirection. In this case it's a reference, though smart pointer types like
|
||||
`Box` can also be used (this will be demonstrated on day 3).
|
||||
|
||||
- At runtime, a `&dyn Pet` is represented as a "fat pointer", i.e. a pair of two
|
||||
pointers: One pointer points to the concrete object that implements `Pet`, and
|
||||
the other points to the vtable for the trait implementation for that type.
|
||||
When calling the `talk` method on `&dyn Pet` the compiler looks up the
|
||||
function pointer for `talk` in the vtable and then invokes the function,
|
||||
passing the pointer to the `Dog` or `Cat` into that function. The compiler
|
||||
doesn't need to know the concrete type of the `Pet` in order to do this.
|
||||
|
||||
- A `dyn Trait` is considered to be "type-erased", because we no longer have
|
||||
compile-time knowledge of what the concrete type is.
|
||||
|
||||
[vtable]: https://en.wikipedia.org/wiki/Virtual_method_table
|
||||
|
||||
</details>
|
@ -2,9 +2,11 @@
|
||||
minutes: 10
|
||||
---
|
||||
|
||||
# Trait Objects
|
||||
# Owned Trait Objects
|
||||
|
||||
Trait objects allow for values of different types, for instance in a collection:
|
||||
We previously saw how trait objects can be used with references, e.g `&dyn Pet`.
|
||||
However, we can also use trait objects with smart pointers like `Box` to create
|
||||
an owned trait object: `Box<dyn Pet>`.
|
||||
|
||||
```rust,editable
|
||||
struct Dog {
|
||||
|
Loading…
Reference in New Issue
Block a user