From 509cb42ece610bdac8eaad26d57fb604dc078623 Mon Sep 17 00:00:00 2001 From: "Carol (Nichols || Goulding)" Date: Sun, 6 May 2018 17:08:20 -0400 Subject: [PATCH] Changes made during 2nd page review of chapter 19 --- second-edition/src/ch13-02-iterators.md | 2 +- .../src/ch19-00-advanced-features.md | 12 +- second-edition/src/ch19-01-unsafe-rust.md | 86 +++++------ .../src/ch19-02-advanced-lifetimes.md | 44 +++--- second-edition/src/ch19-03-advanced-traits.md | 139 +++++++++--------- second-edition/src/ch19-04-advanced-types.md | 55 ++++--- ...ch19-05-advanced-functions-and-closures.md | 24 +-- 7 files changed, 180 insertions(+), 182 deletions(-) diff --git a/second-edition/src/ch13-02-iterators.md b/second-edition/src/ch13-02-iterators.md index 671077c3e..b04f43edc 100644 --- a/second-edition/src/ch13-02-iterators.md +++ b/second-edition/src/ch13-02-iterators.md @@ -58,7 +58,7 @@ All iterators implement a trait named `Iterator` that is defined in the standard library. The definition of the trait looks like this: ```rust -trait Iterator { +pub trait Iterator { type Item; fn next(&mut self) -> Option; diff --git a/second-edition/src/ch19-00-advanced-features.md b/second-edition/src/ch19-00-advanced-features.md index 8465edf9f..683e33526 100644 --- a/second-edition/src/ch19-00-advanced-features.md +++ b/second-edition/src/ch19-00-advanced-features.md @@ -10,13 +10,13 @@ make sure you have a grasp of all the features Rust has to offer. In this chapter, we’ll cover: -* Unsafe Rust: How to opt out of some of Rust’s guarantees and take +* Unsafe Rust: how to opt out of some of Rust’s guarantees and take responsibility for manually upholding those guarantees -* Advanced lifetimes: Syntax for complex lifetime situations -* Advanced traits: Associated types, default type parameters, fully qualified +* Advanced lifetimes: syntax for complex lifetime situations +* Advanced traits: associated types, default type parameters, fully qualified syntax, supertraits, and the newtype pattern in relation to traits -* Advanced types: More about the newtype pattern, type aliases, the *never* - type, and dynamically sized types -* Advanced functions and closures: Function pointers and returning closures +* Advanced types: more about the newtype pattern, type aliases, the never type, + and dynamically sized types +* Advanced functions and closures: function pointers and returning closures It’s a panoply of Rust features with something for everyone! Let’s dive in! diff --git a/second-edition/src/ch19-01-unsafe-rust.md b/second-edition/src/ch19-01-unsafe-rust.md index 2b66a8a93..42d5deda3 100644 --- a/second-edition/src/ch19-01-unsafe-rust.md +++ b/second-edition/src/ch19-01-unsafe-rust.md @@ -7,27 +7,27 @@ and works just like regular Rust, but gives us extra superpowers. Unsafe Rust exists because, by nature, static analysis is conservative. When the compiler tries to determine whether or not code upholds the guarantees, -it’s better for it to reject some valid programs rather than accepting some +it’s better for it to reject some valid programs rather than accept some invalid programs. Although the code might be okay, as far as Rust is able to -tell, it’s not! In these cases, we can use unsafe code to tell the compiler, -“trust me, I know what I’m doing.” The downside is that we use it at our own -risk: if we use unsafe code incorrectly, problems due to memory unsafety, such +tell, it’s not! In these cases, you can use unsafe code to tell the compiler, +“Trust me, I know what I’m doing.” The downside is that you use it at your own +risk: if you use unsafe code incorrectly, problems due to memory unsafety, such as null pointer dereferencing, can occur. Another reason Rust has an unsafe alter ego is that the underlying computer -hardware is inherently unsafe. If Rust didn’t let us do unsafe operations, we -couldn’t do certain tasks. Rust needs to allow us to do low-level systems +hardware is inherently unsafe. If Rust didn’t let you do unsafe operations, you +couldn’t do certain tasks. Rust needs to allow you to do low-level systems programming, such as directly interacting with the operating system or even -writing our own operating system. Working with low-level systems programming is -one of the goals of the language. Let’s explore what we can do with unsafe Rust -and how to do it. +writing your own operating system. Working with low-level systems programming +is one of the goals of the language. Let’s explore what we can do with unsafe +Rust and how to do it. ### Unsafe Superpowers -To switch to unsafe Rust, we use the `unsafe` keyword, and then start a new -block that holds the unsafe code. We can take four actions in unsafe Rust, -which we call *unsafe superpowers*, that we can’t in safe Rust. Those -superpowers include the ability to: +To switch to unsafe Rust, use the `unsafe` keyword and then start a new block +that holds the unsafe code. You can take four actions in unsafe Rust, called +*unsafe superpowers*, that you can’t in safe Rust. Those superpowers include +the ability to: * Dereference a raw pointer * Call an unsafe function or method @@ -36,17 +36,17 @@ superpowers include the ability to: It’s important to understand that `unsafe` doesn’t turn off the borrow checker or disable any other of Rust’s safety checks: if you use a reference in unsafe -code, it will still be checked. The `unsafe` keyword only gives us access to +code, it will still be checked. The `unsafe` keyword only gives you access to these four features that are then not checked by the compiler for memory -safety. We still get some degree of safety inside of an unsafe block. +safety. You’ll still get some degree of safety inside of an unsafe block. In addition, `unsafe` does not mean the code inside the block is necessarily dangerous or that it will definitely have memory safety problems: the intent is -that as the programmer, we’ll ensure the code inside an `unsafe` block will +that as the programmer, you’ll ensure the code inside an `unsafe` block will access memory in a valid way. People are fallible, and mistakes will happen, but by requiring these four -unsafe operations to be inside blocks annotated with `unsafe` we’ll know that +unsafe operations to be inside blocks annotated with `unsafe` you’ll know that any errors related to memory safety must be within an `unsafe` block. Keep `unsafe` blocks small; you’ll be thankful later when you investigate memory bugs. @@ -60,7 +60,7 @@ from leaking out into all the places that you or your users might want to use the functionality implemented with `unsafe` code, because using a safe abstraction is safe. -Let’s look at each of the four unsafe superpowers in turn: we’ll also look at +Let’s look at each of the four unsafe superpowers in turn. We’ll also look at some abstractions that provide a safe interface to unsafe code. ### Dereferencing a Raw Pointer @@ -70,10 +70,10 @@ compiler ensures references are always valid. Unsafe Rust has two new types called *raw pointers* that are similar to references. As with references, raw pointers can be immutable or mutable and are written as `*const T` and `*mut T`, respectively. The asterisk isn’t the dereference operator; it’s part of the -type name. In the context of raw pointers, “immutable” means that the pointer +type name. In the context of raw pointers, *immutable* means that the pointer can’t be directly assigned to after being dereferenced. -Different from references and smart pointers, keep in mind that raw pointers: +Different from references and smart pointers, raw pointers: * Are allowed to ignore the borrowing rules by having both immutable and mutable pointers or multiple mutable pointers to the same location @@ -81,8 +81,8 @@ Different from references and smart pointers, keep in mind that raw pointers: * Are allowed to be null * Don’t implement any automatic cleanup -By opting out of having Rust enforce these guarantees, we can make the -trade-off of giving up guaranteed safety to gain performance or the ability to +By opting out of having Rust enforce these guarantees, you can give up +guaranteed safety in exchange for greater performance or the ability to interface with another language or hardware where Rust’s guarantees don’t apply. Listing 19-1 shows how to create an immutable and a mutable raw pointer from @@ -112,7 +112,7 @@ Listing 19-2 shows how to create a raw pointer to an arbitrary location in memory. Trying to use arbitrary memory is undefined: there might be data at that address or there might not, the compiler might optimize the code so there is no memory access, or the program might error with a segmentation fault. -Usually, there is no good reason to write code like this, but it is possible: +Usually, there is no good reason to write code like this, but it is possible. ```rust let address = 0x012345usize; @@ -144,16 +144,16 @@ unsafe { Creating a pointer does no harm; it’s only when we try to access the value that it points at that we might end up dealing with an invalid value. -Note also that in Listing 19-1 and 19-3 we created `*const i32` and `*mut i32` +Note also that in Listing 19-1 and 19-3, we created `*const i32` and `*mut i32` raw pointers that both pointed to the same memory location, where `num` is stored. If we instead tried to create an immutable and a mutable reference to `num`, the code would not have compiled because Rust’s ownership rules don’t allow a mutable reference at the same time as any immutable references. With raw pointers, we can create a mutable pointer and an immutable pointer to the -same location, and change data through the mutable pointer, potentially -creating a data race. Be careful! +same location and change data through the mutable pointer, potentially creating +a data race. Be careful! -With all of these dangers, why would we ever use raw pointers? One major use +With all of these dangers, why would you ever use raw pointers? One major use case is when interfacing with C code, as you’ll see in the next section, “Calling an Unsafe Function or Method.” Another case is when building up safe abstractions that the borrow checker doesn’t understand. We’ll introduce unsafe @@ -245,17 +245,17 @@ fn split_at_mut(slice: &mut [i32], mid: usize) -> (&mut [i32], &mut [i32]) { Listing 19-5: An attempted implementation of `split_at_mut` using only safe Rust -This function first gets the total length of the slice, then it asserts that -the index given as a parameter is within the slice by checking that it’s less -than or equal to the length. The assertion means that if we pass an index that -is greater than the index to split the slice at, the function will panic before -it attempts to use that index. +This function first gets the total length of the slice. Then it asserts that +the index given as a parameter is within the slice by checking whether it’s +less than or equal to the length. The assertion means that if we pass an index +that is greater than the index to split the slice at, the function will panic +before it attempts to use that index. Then we return two mutable slices in a tuple: one from the start of the original slice to the `mid` index and another from `mid` to the end of the slice. -When we try to compile the code in Listing 19-5, we’ll get an error: +When we try to compile the code in Listing 19-5, we’ll get an error. ```text error[E0499]: cannot borrow `*slice` as mutable more than once at a time @@ -306,11 +306,11 @@ in the variable `ptr`. We keep the assertion that the `mid` index is within the slice. Then we get to the unsafe code: the `slice::from_raw_parts_mut` function takes a raw pointer -and a length, and creates a slice. We use this function to create a slice that -starts from `ptr` and is `mid` items long. Then we call the `offset` method on -`ptr` with `mid` as an argument to get a raw pointer that starts at `mid`, and -we create a slice using that pointer and the remaining number of items after -`mid` as the length. +and a length, and it creates a slice. We use this function to create a slice +that starts from `ptr` and is `mid` items long. Then we call the `offset` +method on `ptr` with `mid` as an argument to get a raw pointer that starts at +`mid`, and we create a slice using that pointer and the remaining number of +items after `mid` as the length. The function `slice::from_raw_parts_mut` is unsafe because it takes a raw pointer and must trust that this pointer is valid. The `offset` method on raw @@ -330,7 +330,7 @@ data this function has access to. In contrast, the use of `slice::from_raw_parts_mut` in Listing 19-7 would likely crash when the slice is used. This code takes an arbitrary memory -location and creates a slice ten thousand items long: +location and creates a slice 10,000 items long. ```rust use std::slice; @@ -485,16 +485,16 @@ races. With mutable data that is globally accessible, it’s difficult to ensure there are no data races, which is why Rust considers mutable static variables to be unsafe. Where possible, it’s preferable to use the concurrency techniques and -thread-safe smart pointers we discussed in Chapter 16, so the compiler checks +thread-safe smart pointers we discussed in Chapter 16 so the compiler checks that data accessed from different threads is done safely. ### Implementing an Unsafe Trait -The final action that only works with `unsafe` is implementing an unsafe trait. +The final action that works only with `unsafe` is implementing an unsafe trait. A trait is unsafe when at least one of its methods has some invariant that the compiler can’t verify. We can declare that a trait is `unsafe` by adding the -`unsafe` keyword before `trait`; then implementation of the trait must be -marked as `unsafe` too, as shown in Listing 19-11. +`unsafe` keyword before `trait` and marking the implementation of the trait as +`unsafe` too, as shown in Listing 19-11. ```rust unsafe trait Foo { diff --git a/second-edition/src/ch19-02-advanced-lifetimes.md b/second-edition/src/ch19-02-advanced-lifetimes.md index acb15c56d..e080e42fd 100644 --- a/second-edition/src/ch19-02-advanced-lifetimes.md +++ b/second-edition/src/ch19-02-advanced-lifetimes.md @@ -6,12 +6,12 @@ lifetimes of different references relate. You saw how every reference has a lifetime, but most of the time, Rust will let you elide lifetimes. Now we’ll look at three advanced features of lifetimes that we haven’t covered yet: -* Lifetime subtyping: Ensures that one lifetime outlives another lifetime -* Lifetime bounds: Specifies a lifetime for a reference to a generic type -* Inference of trait object lifetimes: How the compiler infers trait object - lifetimes and when they need to be specified +* Lifetime subtyping: ensures that one lifetime outlives another lifetime +* Lifetime bounds: specifies a lifetime for a reference to a generic type +* Inference of trait object lifetimes: allows the compiler to infer trait + object lifetimes and when they need to be specified -### Lifetime Subtyping Ensures One Lifetime Outlives Another +### Ensuring One Lifetime Outlives Another with Lifetime Subtyping *Lifetime subtyping* specifies that one lifetime should outlive another lifetime. To explore lifetime subtyping, imagine we want to write a parser. @@ -44,7 +44,7 @@ Compiling the code results in errors because Rust expects lifetime parameters on the string slice in `Context` and the reference to a `Context` in `Parser`. For simplicity’s sake, the `parse` function returns `Result<(), &str>`. That -is, the function will do nothing on success, and on failure will return the +is, the function will do nothing on success and, on failure, will return the part of the string slice that didn’t parse correctly. A real implementation would provide more error information and would return a structured data type when parsing succeeds. We won’t be discussing those details because they aren’t @@ -88,14 +88,14 @@ impl<'a> Parser<'a> { `Parser` with lifetime parameters This code compiles just fine. It tells Rust that a `Parser` holds a reference -to a `Context` with lifetime `'a`, and that `Context` holds a string slice that +to a `Context` with lifetime `'a` and that `Context` holds a string slice that also lives as long as the reference to the `Context` in `Parser`. Rust’s compiler error message stated that lifetime parameters were required for these references, and we’ve now added lifetime parameters. Next, in Listing 19-14, we’ll add a function that takes an instance of `Context`, uses a `Parser` to parse that context, and returns what `parse` -returns. This code doesn’t quite work: +returns. This code doesn’t quite work. Filename: src/lib.rs @@ -191,18 +191,18 @@ string slice that `Context` holds is the same as that of the lifetime of the reference to `Context` that `Parser` holds. The `parse_context` function can’t see that within the `parse` function, the -string slice returned will outlive `Context` and `Parser`, and that the +string slice returned will outlive `Context` and `Parser` and that the reference `parse_context` returns refers to the string slice, not to `Context` or `Parser`. By knowing what the implementation of `parse` does, we know that the only -reason the return value of `parse` is tied to the `Parser` is because it’s -referencing the `Parser`’s `Context`, which is referencing the string slice. -So, it’s really the lifetime of the string slice that `parse_context` needs to -care about. We need a way to tell Rust that the string slice in `Context` and -the reference to the `Context` in `Parser` have different lifetimes and that -the return value of `parse_context` is tied to the lifetime of the string slice -in `Context`. +reason the return value of `parse` is tied to the `Parser` instance is that +it’s referencing the `Parser` instance’s `Context`, which is referencing the +string slice. So, it’s really the lifetime of the string slice that +`parse_context` needs to care about. We need a way to tell Rust that the string +slice in `Context` and the reference to the `Context` in `Parser` have +different lifetimes and that the return value of `parse_context` is tied to the +lifetime of the string slice in `Context`. First, we’ll try giving `Parser` and `Context` different lifetime parameters, as shown in Listing 19-15. We’ll use `'s` and `'c` as lifetime parameter names @@ -298,7 +298,7 @@ lifetime of the string slice is longer than the reference to the `Context`. That was a very long-winded example, but as we mentioned at the start of this chapter, Rust’s advanced features are very specific. You won’t often need the syntax we described in this example, but in such situations, you’ll know how to -refer to something you have a reference to. +refer to something and give it the necessary lifetime. ### Lifetime Bounds on References to Generic Types @@ -312,7 +312,7 @@ As an example, consider a type that is a wrapper over references. Recall the section in Chapter 15: its `borrow` and `borrow_mut` methods return the types `Ref` and `RefMut`, respectively. These types are wrappers over references that keep track of the borrowing rules at runtime. The definition of the `Ref` -struct is shown in Listing 19-16, without lifetime bounds for now: +struct is shown in Listing 19-16, without lifetime bounds for now. Filename: src/lib.rs @@ -321,7 +321,7 @@ struct Ref<'a, T>(&'a T); ``` Listing 19-16: Defining a struct to wrap a reference to a -generic type, without lifetime bounds to start +generic type, without lifetime bounds Without explicitly constraining the lifetime `'a` in relation to the generic parameter `T`, Rust will error because it doesn’t know how long the generic @@ -355,7 +355,7 @@ consider adding an explicit lifetime bound `T: 'a` so that the reference type ``` Listing 19-17 shows how to apply this advice by specifying the lifetime bound -when we declare the generic type `T`: +when we declare the generic type `T`. ```rust struct Ref<'a, T: 'a>(&'a T); @@ -398,7 +398,7 @@ happens if the type implementing the trait in the trait object has a lifetime of its own. Consider Listing 19-19 where we have a trait `Red` and a struct `Ball`. The `Ball` struct holds a reference (and thus has a lifetime parameter) and also implements trait `Red`. We want to use an instance of `Ball` as the -trait object `Box`: +trait object `Box`. Filename: src/main.rs @@ -430,7 +430,7 @@ rules for working with lifetimes and trait objects: is `'a`. * With a single `T: 'a` clause, the default lifetime of the trait object is `'a`. -* With multiple `T: 'a`-like clauses, there is no default lifetime; we must be +* With multiple clauses like `T: 'a`, there is no default lifetime; we must be explicit. When we must be explicit, we can add a lifetime bound on a trait object like diff --git a/second-edition/src/ch19-03-advanced-traits.md b/second-edition/src/ch19-03-advanced-traits.md index ee5311206..7c1531031 100644 --- a/second-edition/src/ch19-03-advanced-traits.md +++ b/second-edition/src/ch19-03-advanced-traits.md @@ -4,7 +4,7 @@ We first covered traits in the “Traits: Defining Shared Behavior” section of Chapter 10, but as with lifetimes, we didn’t discuss the more advanced details. Now that you know more about Rust, we can get into the nitty-gritty. -### Associated Types Specify Placeholder Types in Trait Definitions +### Specifying Placeholder Types in Trait Definitions with Associated Types *Associated types* connect a type placeholder with a trait such that the trait method definitions can use these placeholder types in their signatures. The @@ -15,7 +15,7 @@ trait is implemented. We’ve described most of the advanced features in this chapter as being rarely needed. Associated types are somewhere in the middle: they’re used more rarely -than features explained in the rest of the book, but more commonly than many of +than features explained in the rest of the book but more commonly than many of the other features discussed in this chapter. One example of a trait with an associated type is the `Iterator` trait that the @@ -28,6 +28,7 @@ shown in Listing 19-20. ```rust pub trait Iterator { type Item; + fn next(&mut self) -> Option; } ``` @@ -40,11 +41,9 @@ that it will return values of type `Option`. Implementors of the `Iterator` trait will specify the concrete type for `Item`, and the `next` method will return an `Option` containing a value of that concrete type. -#### Associated Types vs. Generics - -Associated types might seem like a similar concept to generics, in that they -allow us to define a function without specifying what types it can handle. So -why use associated types? +Associated types might seem like a similar concept to generics, in that the +latter allow us to define a function without specifying what types it can +handle. So why use associated types? Let’s examine the difference between the two concepts with an example from Chapter 13 that implements the `Iterator` trait on the `Counter` struct. In @@ -60,8 +59,8 @@ impl Iterator for Counter { // --snip-- ``` -This syntax seems comparable to generics. So why not just define the `Iterator` -trait with generics, as shown in Listing 19-21? +This syntax seems comparable to that of generics. So why not just define the +`Iterator` trait with generics, as shown in Listing 19-21? ```rust pub trait Iterator { @@ -73,13 +72,13 @@ pub trait Iterator { `Iterator` trait using generics The difference is that when using generics, as in Listing 19-21, we must -annotate the types in each implementation. The reason is that we can also -implement `Iterator for Counter` or any other type, which would give us -multiple implementations of `Iterator` for `Counter`. In other words, when a -trait has a generic parameter, it can be implemented for a type multiple times, -changing the concrete types of the generic type parameters each time. When we -use the `next` method on `Counter`, we would have to provide type annotations -to indicate which implementation of `Iterator` we want to use. +annotate the types in each implementation; because we can also implement +`Iterator for Counter` or any other type, we could have multiple +implementations of `Iterator` for `Counter`. In other words, when a trait has a +generic parameter, it can be implemented for a type multiple times, changing +the concrete types of the generic type parameters each time. When we use the +`next` method on `Counter`, we would have to provide type annotations to +indicate which implementation of `Iterator` we want to use. With associated types, we don’t need to annotate types because we can’t implement a trait on a type multiple times. In Listing 19-20 with the @@ -155,20 +154,20 @@ trait Add { ``` This code should look generally familiar: a trait with one method and an -associated type. The new part is `RHS=Self` in the angle brackets: this syntax -is called *default type parameters*. The `RHS` generic type parameter (short -for “right hand side”) defines the type of the `rhs` parameter in the `add` -method. If we don’t specify a concrete type for `RHS` when we implement the -`Add` trait, the type of `RHS` will default to `Self`, which will be the type -we’re implementing `Add` on. +associated type. The new part is `RHS=Self`: this syntax is called *default +type parameters*. The `RHS` generic type parameter (short for “right hand +side”) defines the type of the `rhs` parameter in the `add` method. If we don’t +specify a concrete type for `RHS` when we implement the `Add` trait, the type +of `RHS` will default to `Self`, which will be the type we’re implementing +`Add` on. When we implemented `Add` for `Point`, we used the default for `RHS` because we wanted to add two `Point` instances. Let’s look at an example of implementing the `Add` trait where we want to customize the `RHS` type rather than using the default. -We have two structs holding values in different units, `Millimeters` and -`Meters`. We want to add values in millimeters to values in meters and have the +We have two structs, `Millimeters` and `Meters`, holding values in different +units. We want to add values in millimeters to values in meters and have the implementation of `Add` do the conversion correctly. We can implement `Add` for `Millimeters` with `Meters` as the `RHS`, as shown in Listing 19-23. @@ -195,32 +194,32 @@ impl Add for Millimeters { To add `Millimeters` and `Meters`, we specify `impl Add` to set the value of the `RHS` type parameter instead of using the default of `Self`. -We use default type parameters in two main ways: +You’ll use default type parameters in two main ways: * To extend a type without breaking existing code * To allow customization in specific cases most users won’t need The standard library’s `Add` trait is an example of the second purpose: -usually, you’ll add two like types, but the `Add` trait provides the ability -for customizing beyond that. Using a default type parameter in the `Add` trait +usually, you’ll add two like types, but the `Add` trait provides the ability to +customize beyond that. Using a default type parameter in the `Add` trait definition means you don’t have to specify the extra parameter most of the time. In other words, a bit of implementation boilerplate isn’t needed, making it easier to use the trait. -The first purpose is similar to the second but in reverse: if we want to add a -type parameter to an existing trait, we can give it a default to let us extend -the functionality of the trait without breaking the existing implementation -code. +The first purpose is similar to the second but in reverse: if you want to add a +type parameter to an existing trait, you can give it a default to allow +extension of the functionality of the trait without breaking the existing +implementation code. ### Fully Qualified Syntax for Disambiguation: Calling Methods with the Same Name Nothing in Rust prevents a trait from having a method with the same name as -another trait’s method, nor does Rust prevent us from implementing both traits +another trait’s method, nor does Rust prevent you from implementing both traits on one type. It’s also possible to implement a method directly on the type with the same name as methods from traits. -When calling methods with the same name, we need to tell Rust which one we want -to use. Consider the code in Listing 19-24 where we’ve defined two traits, +When calling methods with the same name, you’ll need to tell Rust which one you +want to use. Consider the code in Listing 19-24 where we’ve defined two traits, `Pilot` and `Wizard`, that both have a method called `fly`. We then implement both traits on a type `Human` that already has a method named `fly` implemented on it. Each `fly` method does something different. @@ -257,9 +256,9 @@ impl Human { } ``` -Listing 19-24: Two traits defined to have a `fly` method -and implementations of those traits on the `Human` type in addition to a `fly` -method on `Human` directly +Listing 19-24: Two traits are defined to have a `fly` +method and are implemented on the `Human` type, and a `fly` method is +implemented on `Human` directly When we call `fly` on an instance of `Human`, the compiler defaults to calling the method that is directly implemented on the type, as shown in Listing 19-25. @@ -304,7 +303,7 @@ fn main() { Listing 19-25: Calling `fly` on an instance of `Human` -Running this code will print `*waving arms furiously*`, which shows that Rust +Running this code will print `*waving arms furiously*`, showing that Rust called the `fly` method implemented on `Human` directly. To call the `fly` methods from either the `Pilot` trait or the `Wizard` trait, @@ -355,8 +354,9 @@ want to call Specifying the trait name before the method name clarifies to Rust which implementation of `fly` we want to call. We could also write -`Human::fly(&person)`, which is equivalent to `person.fly()` that we used in -Listing 19-26 but is a bit longer to write if we don’t need to disambiguate. +`Human::fly(&person)`, which is equivalent to the `person.fly()` that we used +in Listing 19-26, but this is a bit longer to write if we don’t need to +disambiguate. Running this code prints the following: @@ -367,12 +367,12 @@ Up! ``` Because the `fly` method takes a `self` parameter, if we had two *types* that -both implement one *trait*, Rust can figure out which implementation of a trait -to use based on the type of `self`. +both implement one *trait*, Rust could figure out which implementation of a +trait to use based on the type of `self`. However, associated functions that are part of traits don’t have a `self` parameter. When two types in the same scope implement that trait, Rust can’t -figure out which type we mean unless we use *fully qualified syntax*. For +figure out which type you mean unless you use *fully qualified syntax*. For example, the `Animal` trait in Listing 19-27 has the associated function `baby_name`, the implementation of `Animal` for the struct `Dog`, and the associated function `baby_name` defined on `Dog` directly. @@ -404,8 +404,8 @@ fn main() { ``` Listing 19-27: A trait with an associated function and a -type that has an associated function with the same name that also implements -the trait +type with an associated function of the same name that also implements the +trait This code is for an animal shelter that wants to name all puppies Spot, which is implemented in the `baby_name` associated function that is defined on `Dog`. @@ -454,9 +454,8 @@ error[E0283]: type annotations required: cannot resolve `_: Animal` ``` To disambiguate and tell Rust that we want to use the implementation of -`Animal` for `Dog`, we need to use *fully qualified syntax*, which is the most -specific we can be when calling a function. Listing 19-29 demonstrates how to -use fully qualified syntax. +`Animal` for `Dog`, we need to use fully qualified syntax. Listing 19-29 +demonstrates how to use fully qualified syntax. Filename: src/main.rs @@ -504,18 +503,18 @@ In general, fully qualified syntax is defined as follows: ``` For associated functions, there would not be a `receiver`: there would only be -the list of other arguments. We could use fully qualified syntax everywhere -that we call functions or methods. However, we’re allowed to omit any part of -this syntax that Rust can figure out from other information in the program. We +the list of other arguments. You could use fully qualified syntax everywhere +that you call functions or methods. However, you’re allowed to omit any part of +this syntax that Rust can figure out from other information in the program. You only need to use this more verbose syntax in cases where there are multiple implementations that use the same name and Rust needs help to identify which -implementation we want to call. +implementation you want to call. ### Using Supertraits to Require One Trait’s Functionality Within Another Trait -Sometimes, we might need one trait to use another trait’s functionality. In -this case, we need to rely on the dependent trait also being implemented. The -trait we’re relying on is a *supertrait* of the trait we’re implementing. +Sometimes, you might need one trait to use another trait’s functionality. In +this case, you need to rely on the dependent trait’s also being implemented. +The trait you rely on is a *supertrait* of the trait you’re implementing. For example, let’s say we want to make an `OutlinePrint` trait with an `outline_print` method that will print a value framed in asterisks. That is, @@ -533,10 +532,10 @@ call `outline_print` on a `Point` instance that has `1` for `x` and `3` for In the implementation of `outline_print`, we want to use the `Display` trait’s functionality. Therefore, we need to specify that the `OutlinePrint` trait will -only work for types that also implement `Display` and provide the functionality +work only for types that also implement `Display` and provide the functionality that `OutlinePrint` needs. We can do that in the trait definition by specifying `OutlinePrint: Display`. This technique is similar to adding a trait bound to -the trait. Listing 19-30 shows an implementation of the `OutlinePrint` trait: +the trait. Listing 19-30 shows an implementation of the `OutlinePrint` trait. Filename: src/main.rs @@ -561,9 +560,10 @@ requires the functionality from `Display` Because we’ve specified that `OutlinePrint` requires the `Display` trait, we can use the `to_string` function that is automatically implemented for any type -that implements `Display`. If we tried to use `to_string` without adding `: -Display` after the trait name, we’d get an error saying that no method named -`to_string` was found for the type `&Self` in the current scope. +that implements `Display`. If we tried to use `to_string` without adding a +colon and specifying the `Display` trait after the trait name, we’d get an +error saying that no method named `to_string` was found for the type `&Self` in +the current scope. Let’s see what happens when we try to implement `OutlinePrint` on a type that doesn’t implement `Display`, such as the `Point` struct: @@ -617,7 +617,7 @@ Then implementing the `OutlinePrint` trait on `Point` will compile successfully, and we can call `outline_print` on a `Point` instance to display it within an outline of asterisks. -### The Newtype Pattern to Implement External Traits on External Types +### Using the Newtype Pattern to Implement External Traits on External Types In Chapter 10 in the “Implementing a Trait on a Type” section, we mentioned the orphan rule that states we’re allowed to implement a trait on a type as long as @@ -665,15 +665,14 @@ tuple. Then we can use the functionality of the `Display` type on `Wrapper`. The downside of using this technique is that `Wrapper` is a new type, so it doesn’t have the methods of the value it’s holding. We would have to implement -all the methods of `Vec` directly on `Wrapper` so it can delegate to `self.0`, -allowing us to treat `Wrapper` exactly like a `Vec`. If we wanted the new type -to have every method the inner type has, implementing the `Deref` trait -(discussed in Chapter 15 in the “Treating Smart Pointers like Regular -References with the `Deref` Trait” section) on the `Wrapper` to return the -inner type would be a solution. If we don’t want the `Wrapper` type to have all -the methods of the inner type, in order to restrict the `Wrapper` type’s -behavior for example, we would have to implement just the methods we do want -manually. +all the methods of `Vec` directly on `Wrapper` such that the methods delegate +to `self.0`, which would allow us to treat `Wrapper` exactly like a `Vec`. If +we wanted the new type to have every method the inner type has, implementing +the `Deref` trait (discussed in Chapter 15 in the “Treating Smart Pointers like +Regular References with the `Deref` Trait” section) on the `Wrapper` to return +the inner type would be a solution. If we don’t want the `Wrapper` type to have +all the methods of the inner type—for example, to restrict the `Wrapper` type’s +behavior—we would have to implement just the methods we do want manually. Now you know how the newtype pattern is used in relation to traits; it’s also a useful pattern even when traits are not involved. Let’s switch focus and look diff --git a/second-edition/src/ch19-04-advanced-types.md b/second-edition/src/ch19-04-advanced-types.md index eac00646c..8edfe23d5 100644 --- a/second-edition/src/ch19-04-advanced-types.md +++ b/second-edition/src/ch19-04-advanced-types.md @@ -11,14 +11,13 @@ discuss the `!` type and dynamically sized types. ### Using the Newtype Pattern for Type Safety and Abstraction -The newtype pattern is useful for other tasks beyond what we’ve discussed so -far, including statically enforcing that values are never confused and as an -indication of the units of a value. You saw an example of using newtypes to -indicate units in Listing 19-23: recall that the `Millimeters` and `Meters` -structs wrapped `u32` values in a newtype. If we wrote a function with a -parameter of type `Millimeters`, we couldn’t compile a program that -accidentally tried to call that function with a value of type `Meters` or a -plain `u32`. +The newtype pattern is useful for tasks beyond those we’ve discussed so far, +including statically enforcing that values are never confused and indicating +the units of a value. You saw an example of using newtypes to indicate units in +Listing 19-23: recall that the `Millimeters` and `Meters` structs wrapped `u32` +values in a newtype. If we wrote a function with a parameter of type +`Millimeters`, we couldn’t compile a program that accidentally tried to call +that function with a value of type `Meters` or a plain `u32`. Another use of the newtype pattern is in abstracting away some implementation details of a type: the new type can expose a public API that is different from @@ -34,7 +33,7 @@ internally. The newtype pattern is a lightweight way to achieve encapsulation to hide implementation details, which we discussed in the “Encapsulation that Hides Implementation Details” section of Chapter 17. -### Type Aliases Create Type Synonyms +### Creating Type Synonyms with Type Aliases Along with the newtype pattern, Rust provides the ability to declare a *type alias* to give an existing type another name. For this we use the `type` @@ -145,7 +144,7 @@ type Result = Result; ``` Because this declaration is in the `std::io` module, we can use the fully -qualified alias `std::io::Result`; that is, a `Result` with the `E` +qualified alias `std::io::Result`—that is, a `Result` with the `E` filled in as `std::io::Error`. The `Write` trait function signatures end up looking like this: @@ -162,9 +161,9 @@ pub trait Write { The type alias helps in two ways: it makes code easier to write *and* it gives us a consistent interface across all of `std::io`. Because it’s an alias, it’s just another `Result`, which means we can use any methods that work on -`Result` with it, as well as special syntax like `?`. +`Result` with it, as well as special syntax like the `?` operator. -### The `!` Never Type that Never Returns +### The Never Type that Never Returns Rust has a special type named `!` that’s known in type theory lingo as the *empty type* because it has no values. We prefer to call it the *never type* @@ -182,7 +181,7 @@ never are called *diverging functions*. We can’t create values of the type `!` so `bar` can never possibly return. But what use is a type you can never create values for? Recall the code from -Listing 2-5; we’ve reproduced it here in Listing 19-34. +Listing 2-5; we’ve reproduced part of it here in Listing 19-34. ```rust # let guess = "3"; @@ -210,7 +209,7 @@ let guess = match guess.trim().parse() { ``` The type of `guess` in this code would have to be an integer *and* a string, -and Rust requires that `guess` can only have one type. So what does `continue` +and Rust requires that `guess` have only one type. So what does `continue` return? How were we allowed to return a `u32` from one arm and have another arm that ends with `continue` in Listing 19-34? @@ -241,10 +240,10 @@ impl Option { ``` In this code, the same thing happens as in the `match` in Listing 19-34: Rust -sees that `val` has the type `T` and `panic!` has the type `!` so the result of -the overall `match` expression is `T`. This code works because `panic!` doesn’t -produce a value; it ends the program. In the `None` case, we won’t be returning -a value from `unwrap`, so this code is valid. +sees that `val` has the type `T` and `panic!` has the type `!`, so the result +of the overall `match` expression is `T`. This code works because `panic!` +doesn’t produce a value; it ends the program. In the `None` case, we won’t be +returning a value from `unwrap`, so this code is valid. One final expression that has the type `!` is a `loop`: @@ -260,13 +259,13 @@ Here, the loop never ends, so `!` is the value of the expression. However, this wouldn’t be true if we included a `break`, because the loop would terminate when it got to the `break`. -### Dynamically Sized Types and `Sized` +### Dynamically Sized Types and the `Sized` Trait Due to Rust’s need to know certain details, such as how much space to allocate for a value of a particular type, there is a corner of its type system that can be confusing: the concept of *dynamically sized types*. Sometimes referred to as *DSTs* or *unsized types*, these types let us write code using values whose -size we can only know at runtime. +size we can know only at runtime. Let’s dig into the details of a dynamically sized type called `str`, which we’ve been using throughout the book. That’s right, not `&str`, but `str` on @@ -288,18 +287,18 @@ holding a dynamically sized type. So what do we do? In this case, you already know the answer: we make the types of `s1` and `s2` a `&str` rather than a `str`. Recall that in the “String -Slices” section of Chapter 4 we said the slice data structure stores the +Slices” section of Chapter 4, we said the slice data structure stores the starting position and the length of the slice. So although a `&T` is a single value that stores the memory address of where the `T` is located, a `&str` is *two* values: the address of the `str` and its length. As such, we can know the size of a `&str` value at compile time: it’s -two times the size of a `usize` in length. That is, we always know the size of -a `&str`, no matter how long the string it refers to is. In general, this is -the way in which dynamically sized types are used in Rust: they have an extra -bit of metadata that stores the size of the dynamic information. The golden -rule of dynamically sized types is that we must always put values of -dynamically sized types behind a pointer of some kind. +twice the length of a `usize`. That is, we always know the size of a `&str`, no +matter how long the string it refers to is. In general, this is the way in +which dynamically sized types are used in Rust: they have an extra bit of +metadata that stores the size of the dynamic information. The golden rule of +dynamically sized types is that we must always put values of dynamically sized +types behind a pointer of some kind. We can combine `str` with all kinds of pointers: for example, `Box` or `Rc`. In fact, you’ve seen this before but with a different dynamically @@ -329,7 +328,7 @@ fn generic(t: T) { } ``` -By default, generic functions will only work on types that have a known size at +By default, generic functions will work only on types that have a known size at compile time. However, you can use the following special syntax to relax this restriction: diff --git a/second-edition/src/ch19-05-advanced-functions-and-closures.md b/second-edition/src/ch19-05-advanced-functions-and-closures.md index 6a72ea5e7..6e9d0b1c4 100644 --- a/second-edition/src/ch19-05-advanced-functions-and-closures.md +++ b/second-edition/src/ch19-05-advanced-functions-and-closures.md @@ -6,12 +6,12 @@ closures, which include function pointers and returning closures. ### Function Pointers We’ve talked about how to pass closures to functions; you can also pass regular -functions to functions! This technique is useful when we want to pass a -function we’ve already defined rather than defining a new closure. We do this -using function pointers to allow us to use functions as arguments to other +functions to functions! This technique is useful when you want to pass a +function you’ve already defined rather than defining a new closure. Doing this +with function pointers will allow you to use functions as arguments to other functions. Functions coerce to the type `fn` (with a lowercase f), not to be -confused with the `Fn` closure trait. The `fn` type is called a function -pointer. The syntax for specifying that a parameter is a function pointer is +confused with the `Fn` closure trait. The `fn` type is called a *function +pointer*. The syntax for specifying that a parameter is a function pointer is similar to that of closures, as shown in Listing 19-35. Filename: src/main.rs @@ -45,7 +45,7 @@ parameter type directly rather than declaring a generic type parameter with one of the `Fn` traits as a trait bound. Function pointers implement all three of the closure traits (`Fn`, `FnMut`, and -`FnOnce`), so we can always pass a function pointer as an argument for a +`FnOnce`), so you can always pass a function pointer as an argument for a function that expects a closure. It’s best to write functions using a generic type and one of the closure traits so your functions can accept either functions or closures. @@ -54,7 +54,7 @@ An example of where you would want to only accept `fn` and not closures is when interfacing with external code that doesn’t have closures: C functions can accept functions as arguments, but C doesn’t have closures. -As an example of where we can use either a closure defined inline or a named +As an example of where you could use either a closure defined inline or a named function, let’s look at a use of `map`. To use the `map` function to turn a vector of numbers into a vector of strings, we could use a closure, like this: @@ -88,12 +88,12 @@ up compiling to the same code, so use whichever style is clearer to you. ### Returning Closures -Closures are represented by traits, which means we can’t return closures -directly. In most cases where we might want to return a trait, we can instead +Closures are represented by traits, which means you can’t return closures +directly. In most cases where you might want to return a trait, you can instead use the concrete type that implements the trait as the return value of the -function. But we can’t do that with closures because they don’t have a concrete -type that is returnable; we’re not allowed to use the function pointer `fn` as -a return type, for example. +function. But you can’t do that with closures because they don’t have a +concrete type that is returnable; you’re not allowed to use the function +pointer `fn` as a return type, for example. The following code tries to return a closure directly, but it won’t compile: