mirror of
https://github.com/google/comprehensive-rust.git
synced 2025-03-22 23:05:22 +02:00
Minor fixes for Day 3 Morning (#532)
* don't explain default trait methods early * talk about Iterator before IntoIterator * Defer discussion of trait objects to that chapter * be more specific about turbofish, in speaker notes
This commit is contained in:
parent
8c56c949ef
commit
780368b4f7
@ -16,7 +16,6 @@ fn main() {
|
||||
}
|
||||
```
|
||||
|
||||
* `impl Trait` cannot be used with the `::<>` turbo fish syntax.
|
||||
* `impl Trait` allows you to work with types which you cannot name.
|
||||
|
||||
<details>
|
||||
@ -24,15 +23,22 @@ fn main() {
|
||||
The meaning of `impl Trait` is a bit different in the different positions.
|
||||
|
||||
* For a parameter, `impl Trait` is like an anonymous generic parameter with a trait bound.
|
||||
|
||||
* For a return type, it means that the return type is some concrete type that implements the trait,
|
||||
without naming the type. This can be useful when you don't want to expose the concrete type in a
|
||||
public API.
|
||||
|
||||
Inference is hard in return position. A function returning `impl Foo` picks
|
||||
the concrete type it returns, without writing it out in the source. A function
|
||||
returning a generic type like `collect<B>() -> B` can return any type
|
||||
satisfying `B`, and the caller may need to choose one, such as with `let x:
|
||||
Vec<_> = foo.collect()` or with the turbofish, `foo.collect::<Vec<_>>()`.
|
||||
|
||||
This example is great, because it uses `impl Display` twice. It helps to explain that
|
||||
nothing here enforces that it is _the same_ `impl Display` type. If we used a single
|
||||
`T: Display`, it would enforce the constraint that input `T` and return `T` type are the same type.
|
||||
It would not work for this particular function, as the type we expect as input is likely not
|
||||
what `format!` returns. If we wanted to do the same via `: Display` syntax, we'd need two
|
||||
independent generic parameters.
|
||||
|
||||
|
||||
</details>
|
||||
|
@ -19,7 +19,7 @@ However, how can we store a collection of mixed types which implement `Display`?
|
||||
|
||||
```rust,editable,compile_fail
|
||||
fn main() {
|
||||
let xs = vec![123, "Hello"];
|
||||
let displayables = vec![123, "Hello"];
|
||||
}
|
||||
```
|
||||
|
||||
@ -29,20 +29,20 @@ For this, we need _trait objects_:
|
||||
use std::fmt::Display;
|
||||
|
||||
fn main() {
|
||||
let xs: Vec<Box<dyn Display>> = vec![Box::new(123), Box::new("Hello")];
|
||||
for x in xs {
|
||||
let displayables: Vec<Box<dyn Display>> = vec![Box::new(123), Box::new("Hello")];
|
||||
for x in displayables {
|
||||
println!("x: {x}");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Memory layout after allocating `xs`:
|
||||
Memory layout after allocating `displayables`:
|
||||
|
||||
```bob
|
||||
Stack Heap
|
||||
.- - - - - - - - - - - - - -. .- - - - - - - - - - - - - - - - - - - - - - - -.
|
||||
: : : :
|
||||
: xs : : :
|
||||
: displayables : : :
|
||||
: +-----------+-------+ : : +-----+-----+ :
|
||||
: | ptr | o---+---+-----+-->| o o | o o | :
|
||||
: | len | 2 | : : +-|-|-+-|-|-+ :
|
||||
@ -84,3 +84,19 @@ fn main() {
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
<details>
|
||||
|
||||
* Types that implement a given trait may be of different sizes. This makes it impossible to have things like `Vec<Display>` in the example above.
|
||||
* `dyn Display` is a way to tell the compiler about a dynamically sized type that implements `Display`.
|
||||
* In the example, `displayables` holds *fat pointers* to objects that implement `Display`. The fat pointer consists of two components, a pointer to the actual object and a pointer to the virtual method table for the `Display` implementation of that particular object.
|
||||
* Compare these outputs in the above example:
|
||||
```rust,ignore
|
||||
use std::fmt::Display;
|
||||
println!("{}", std::mem::size_of::<u32>());
|
||||
println!("{}", std::mem::size_of::<&u32>());
|
||||
println!("{}", std::mem::size_of::<&dyn Display>());
|
||||
println!("{}", std::mem::size_of::<Box<dyn Display>>());
|
||||
```
|
||||
|
||||
</details>
|
||||
|
@ -3,51 +3,45 @@
|
||||
Rust lets you abstract over types with traits. They're similar to interfaces:
|
||||
|
||||
```rust,editable
|
||||
trait Greet {
|
||||
fn say_hello(&self);
|
||||
trait Pet {
|
||||
fn name(&self) -> String;
|
||||
}
|
||||
|
||||
struct Dog {
|
||||
name: String,
|
||||
}
|
||||
|
||||
struct Cat; // No name, cats won't respond to it anyway.
|
||||
struct Cat;
|
||||
|
||||
impl Greet for Dog {
|
||||
fn say_hello(&self) {
|
||||
println!("Wuf, my name is {}!", self.name);
|
||||
impl Pet for Dog {
|
||||
fn name(&self) -> String {
|
||||
self.name.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl Greet for Cat {
|
||||
fn say_hello(&self) {
|
||||
println!("Miau!");
|
||||
impl Pet for Cat {
|
||||
fn name(&self) -> String {
|
||||
String::from("The cat") // No name, cats won't respond to it anyway.
|
||||
}
|
||||
}
|
||||
|
||||
fn greet(pet: &impl Pet) {
|
||||
println!("Who's a cutie? {} is!", pet.name());
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let pets: Vec<Box<dyn Greet>> = vec![
|
||||
Box::new(Dog { name: String::from("Fido") }),
|
||||
Box::new(Cat),
|
||||
];
|
||||
for pet in pets {
|
||||
pet.say_hello();
|
||||
}
|
||||
let fido = Dog { name: "Fido".into() };
|
||||
greet(&fido);
|
||||
|
||||
let captain_floof = Cat;
|
||||
greet(&captain_floof);
|
||||
}
|
||||
```
|
||||
|
||||
<details>
|
||||
|
||||
* Traits may specify pre-implemented (default) methods and methods that users are required to implement themselves. Methods with default implementations can rely on required methods.
|
||||
* Types that implement a given trait may be of different sizes. This makes it impossible to have things like `Vec<Greet>` in the example above.
|
||||
* `dyn Greet` is a way to tell the compiler about a dynamically sized type that implements `Greet`.
|
||||
* In the example, `pets` holds Fat Pointers to objects that implement `Greet`. The Fat Pointer consists of two components, a pointer to the actual object and a pointer to the virtual method table for the `Greet` implementation of that particular object.
|
||||
* Compare these outputs in the above example:
|
||||
```rust,ignore
|
||||
println!("{} {}", std::mem::size_of::<Dog>(), std::mem::size_of::<Cat>());
|
||||
println!("{} {}", std::mem::size_of::<&Dog>(), std::mem::size_of::<&Cat>());
|
||||
println!("{}", std::mem::size_of::<&dyn Greet>());
|
||||
println!("{}", std::mem::size_of::<Box<dyn Greet>>());
|
||||
```
|
||||
* Later sections will get into more detail on generic functions like `greet`.
|
||||
For now, students only need to know that `greet` will operate on a reference
|
||||
to anything that implements `Pet`.
|
||||
|
||||
</details>
|
||||
|
@ -29,13 +29,16 @@ fn main() {
|
||||
|
||||
<details>
|
||||
|
||||
* `IntoIterator` is the trait that makes for loops work. It is implemented by collection types such as
|
||||
`Vec<T>` and references to them such as `&Vec<T>` and `&[T]`. Ranges also implement it.
|
||||
* The `Iterator` trait implements many common functional programming operations over collections
|
||||
(e.g. `map`, `filter`, `reduce`, etc). This is the trait where you can find all the documentation
|
||||
about them. In Rust these functions should produce the code as efficient as equivalent imperative
|
||||
implementations.
|
||||
|
||||
* `IntoIterator` is the trait that makes for loops work. It is implemented by collection types such as
|
||||
`Vec<T>` and references to them such as `&Vec<T>` and `&[T]`. Ranges also implement it. This is why
|
||||
you can iterate over a vector with `for i in some_vec { .. }` but
|
||||
`some_vec.next()` doesn't exist.
|
||||
|
||||
</details>
|
||||
|
||||
[1]: https://doc.rust-lang.org/std/iter/trait.Iterator.html
|
||||
|
Loading…
x
Reference in New Issue
Block a user