mirror of
https://github.com/google/comprehensive-rust.git
synced 2025-04-26 01:04:35 +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)
|
- [Generic Traits](generics/generic-traits.md)
|
||||||
- [Trait Bounds](generics/trait-bounds.md)
|
- [Trait Bounds](generics/trait-bounds.md)
|
||||||
- [`impl Trait`](generics/impl-trait.md)
|
- [`impl Trait`](generics/impl-trait.md)
|
||||||
|
- [`dyn Trait`](generics/dyn-trait.md)
|
||||||
- [Exercise: Generic `min`](generics/exercise.md)
|
- [Exercise: Generic `min`](generics/exercise.md)
|
||||||
- [Solution](generics/solution.md)
|
- [Solution](generics/solution.md)
|
||||||
- [Standard Library Types](std-types.md)
|
- [Standard Library Types](std-types.md)
|
||||||
@ -141,7 +142,7 @@
|
|||||||
- [Smart Pointers](smart-pointers.md)
|
- [Smart Pointers](smart-pointers.md)
|
||||||
- [`Box<T>`](smart-pointers/box.md)
|
- [`Box<T>`](smart-pointers/box.md)
|
||||||
- [`Rc`](smart-pointers/rc.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)
|
- [Exercise: Binary Tree](smart-pointers/exercise.md)
|
||||||
- [Solution](smart-pointers/solution.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
|
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
|
```rust,editable
|
||||||
struct Dog {
|
struct Dog {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user