From 780368b4f713e0825aee52babae744e1a3c510d5 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Tue, 28 Mar 2023 15:42:56 -0400 Subject: [PATCH] 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 --- src/generics/impl-trait.md | 10 ++++++-- src/generics/trait-objects.md | 26 +++++++++++++++---- src/traits.md | 48 +++++++++++++++-------------------- src/traits/iterator.md | 7 +++-- 4 files changed, 55 insertions(+), 36 deletions(-) diff --git a/src/generics/impl-trait.md b/src/generics/impl-trait.md index e4e4105e..c48348dc 100644 --- a/src/generics/impl-trait.md +++ b/src/generics/impl-trait.md @@ -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.
@@ -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` 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::>()`. + 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. - +
diff --git a/src/generics/trait-objects.md b/src/generics/trait-objects.md index 4104dc70..f635e789 100644 --- a/src/generics/trait-objects.md +++ b/src/generics/trait-objects.md @@ -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> = vec![Box::new(123), Box::new("Hello")]; - for x in xs { + let displayables: Vec> = 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() { } ``` + +
+ +* Types that implement a given trait may be of different sizes. This makes it impossible to have things like `Vec` 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::()); + println!("{}", std::mem::size_of::<&u32>()); + println!("{}", std::mem::size_of::<&dyn Display>()); + println!("{}", std::mem::size_of::>()); + ``` + +
diff --git a/src/traits.md b/src/traits.md index 05ea082d..827802ce 100644 --- a/src/traits.md +++ b/src/traits.md @@ -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> = 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); } ```
-* 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` 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::(), std::mem::size_of::()); - println!("{} {}", std::mem::size_of::<&Dog>(), std::mem::size_of::<&Cat>()); - println!("{}", std::mem::size_of::<&dyn Greet>()); - println!("{}", std::mem::size_of::>()); - ``` +* 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`.
diff --git a/src/traits/iterator.md b/src/traits/iterator.md index c4108f90..b730c354 100644 --- a/src/traits/iterator.md +++ b/src/traits/iterator.md @@ -29,13 +29,16 @@ fn main() {
-* `IntoIterator` is the trait that makes for loops work. It is implemented by collection types such as - `Vec` and references to them such as `&Vec` 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` and references to them such as `&Vec` 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. +
[1]: https://doc.rust-lang.org/std/iter/trait.Iterator.html