From 3fa81cda9ff92e89cc43953181cb2d9fa842746f Mon Sep 17 00:00:00 2001 From: "Carol (Nichols || Goulding)" Date: Tue, 22 Mar 2022 21:13:37 -0400 Subject: [PATCH] Propagating edits to chapter 10 back --- src/ch10-00-generics.md | 100 ++++++++++--------- src/ch10-01-syntax.md | 132 +++++++++++++------------ src/ch10-02-traits.md | 174 +++++++++++++++------------------ src/ch10-03-lifetime-syntax.md | 160 +++++++++++++++--------------- 4 files changed, 280 insertions(+), 286 deletions(-) diff --git a/src/ch10-00-generics.md b/src/ch10-00-generics.md index 14f46ad70..0c67a85d9 100644 --- a/src/ch10-00-generics.md +++ b/src/ch10-00-generics.md @@ -1,42 +1,46 @@ # Generic Types, Traits, and Lifetimes Every programming language has tools for effectively handling the duplication -of concepts. In Rust, one such tool is *generics*. Generics are abstract -stand-ins for concrete types or other properties. When we’re writing code, we -can express the behavior of generics or how they relate to other generics -without knowing what will be in their place when compiling and running the code. +of concepts. In Rust, one such tool is *generics*: abstract stand-ins for +concrete types or other properties. We can express the behavior of generics or +how they relate to other generics without knowing what will be in their place +when compiling and running the code. -Similar to the way a function takes parameters with unknown values to run the -same code on multiple concrete values, functions can take parameters of some -generic type instead of a concrete type, like `i32` or `String`. In fact, we’ve +Functions can take parameters of some generic type, instead of a concrete type +like `i32` or `String`, in the same way a function takes parameters with +unknown values to run the same code on multiple concrete values. In fact, we’ve already used generics in Chapter 6 with `Option`, Chapter 8 with `Vec` and `HashMap`, and Chapter 9 with `Result`. In this chapter, you’ll explore how to define your own types, functions, and methods with generics! -First, we’ll review how to extract a function to reduce code duplication. Next, -we’ll use the same technique to make a generic function from two functions that +First, we’ll review how to extract a function to reduce code duplication. We’ll +then use the same technique to make a generic function from two functions that differ only in the types of their parameters. We’ll also explain how to use generic types in struct and enum definitions. Then you’ll learn how to use *traits* to define behavior in a generic way. You -can combine traits with generic types to constrain a generic type to only -those types that have a particular behavior, as opposed to just any type. +can combine traits with generic types to constrain a generic type to accept +only those types that have a particular behavior, as opposed to just any type. -Finally, we’ll discuss *lifetimes*, a variety of generics that give the +Finally, we’ll discuss *lifetimes*: a variety of generics that give the compiler information about how references relate to each other. Lifetimes allow -us to borrow values in many situations while still enabling the compiler to -check that the references are valid. +us to give the compiler enough information about borrowed values so that it can +ensure references will be valid in more situations than it could without our +help. ## Removing Duplication by Extracting a Function -Before diving into generics syntax, let’s first look at how to remove -duplication that doesn’t involve generic types by extracting a function. Then -we’ll apply this technique to extract a generic function! In the same way that -you recognize duplicated code to extract into a function, you’ll start to -recognize duplicated code that can use generics. +Generics allow us to replace specific types with a placeholder that represents +multiple types to remove code duplication. Before diving into generics syntax, +then, let’s first look at how to remove duplication in a way that doesn’t +involve generic types by extracting a function that replaces specific values +with a placeholder that represents multiple values. Then we’ll apply the same +technique to extract a generic function! By looking at how to recognize +duplicated code you can extract into a function, you’ll start to recognize +duplicated code that can use generics. -Consider a short program that finds the largest number in a list, as shown in -Listing 10-1. +We begin with the short program in Listing 10-1 that finds the largest number +in a list. Filename: src/main.rs @@ -44,21 +48,21 @@ Listing 10-1. {{#rustdoc_include ../listings/ch10-generic-types-traits-and-lifetimes/listing-10-01/src/main.rs:here}} ``` -Listing 10-1: Code to find the largest number in a list -of numbers +Listing 10-1: Finding the largest number in a list of +numbers -This code stores a list of integers in the variable `number_list` and places -the first number in the list in a variable named `largest`. Then it iterates -through all the numbers in the list, and if the current number is greater than -the number stored in `largest`, it replaces the number in that variable. -However, if the current number is less than or equal to the largest number seen -so far, the variable doesn’t change, and the code moves on to the next number -in the list. After considering all the numbers in the list, `largest` should -hold the largest number, which in this case is 100. +We store a list of integers in the variable `number_list` and place the first +number in the list in a variable named `largest`. We then iterate through all +the numbers in the list, and if the current number is greater than the number +stored in `largest`, replace the number in that variable. However, if the +current number is less than or equal to the largest number seen so far, the +variable doesn’t change, and the code moves on to the next number in the list. +After considering all the numbers in the list, `largest` should hold the +largest number, which in this case is 100. -To find the largest number in two different lists of numbers, we can duplicate -the code in Listing 10-1 and use the same logic at two different places in the -program, as shown in Listing 10-2. +We've now been tasked with finding the largest number in two different lists of +numbers. To do so, we can choose to duplicate the code in Listing 10-1 and use +the same logic at two different places in the program, as shown in Listing 10-2. Filename: src/main.rs @@ -70,17 +74,18 @@ program, as shown in Listing 10-2. lists of numbers Although this code works, duplicating code is tedious and error prone. We also -have to update the code in multiple places when we want to change it. +have to remember to update the code in multiple places when we want to change +it. -To eliminate this duplication, we can create an abstraction by defining a -function that operates on any list of integers given to it in a parameter. This +To eliminate this duplication, we’ll create an abstraction by defining a +function that operates on any list of integers passed in a parameter. This solution makes our code clearer and lets us express the concept of finding the largest number in a list abstractly. -In Listing 10-3, we extracted the code that finds the largest number into a -function named `largest`. Unlike the code in Listing 10-1, which can find the -largest number in only one particular list, this program can find the largest -number in two different lists. +In Listing 10-3, we extract the code that finds the largest number into a +function named `largest`. Then we call the function to find the largest number +in the two lists from Listing 10-2. We could also use the function on any other +list of `i32` values we might have in the future. Filename: src/main.rs @@ -92,9 +97,9 @@ number in two different lists. in two lists The `largest` function has a parameter called `list`, which represents any -concrete slice of `i32` values that we might pass into the function. As a -result, when we call the function, the code runs on the specific values that we -pass in. Don’t worry about the syntax of the `for` loop for now. We aren’t +concrete slice of `i32` values we might pass into the function. As a result, +when we call the function, the code runs on the specific values that we pass +in. Don’t worry about the syntax of the `for` loop for now. We aren’t referencing a reference to an `i32` here; we’re pattern matching and destructuring each `&i32` that the `for` loop gets so that `item` will be an `i32` inside the loop body. We’ll cover pattern matching in detail in [Chapter @@ -108,10 +113,9 @@ Listing 10-3: inputs and return values of that code in the function signature. 3. Update the two instances of duplicated code to call the function instead. -Next, we’ll use these same steps with generics to reduce code duplication in -different ways. In the same way that the function body can operate on an -abstract `list` instead of specific values, generics allow code to operate on -abstract types. +Next, we’ll use these same steps with generics to reduce code duplication. In +the same way that the function body can operate on an abstract `list` instead +of specific values, generics allow code to operate on abstract types. For example, say we had two functions: one that finds the largest item in a slice of `i32` values and one that finds the largest item in a slice of `char` diff --git a/src/ch10-01-syntax.md b/src/ch10-01-syntax.md index e3b3a6c5b..c2ed68d09 100644 --- a/src/ch10-01-syntax.md +++ b/src/ch10-01-syntax.md @@ -1,6 +1,6 @@ ## Generic Data Types -We can use generics to create definitions for items like function signatures or +We use generics to create definitions for items like function signatures or structs, which we can then use with many different concrete data types. Let’s first look at how to define functions, structs, enums, and methods using generics. Then we’ll discuss how generics affect code performance. @@ -13,7 +13,8 @@ parameters and return value. Doing so makes our code more flexible and provides more functionality to callers of our function while preventing code duplication. Continuing with our `largest` function, Listing 10-4 shows two functions that -both find the largest value in a slice. +both find the largest value in a slice. We'll then combine these into a single +function that uses generics. Filename: src/main.rs @@ -29,9 +30,9 @@ the largest `i32` in a slice. The `largest_char` function finds the largest `char` in a slice. The function bodies have the same code, so let’s eliminate the duplication by introducing a generic type parameter in a single function. -To parameterize the types in the new function we’ll define, we need to name the -type parameter, just as we do for the value parameters to a function. You can -use any identifier as a type parameter name. But we’ll use `T` because, by +To parameterize the types in a new single function, we need to name the type +parameter, just as we do for the value parameters to a function. You can use +any identifier as a type parameter name. But we’ll use `T` because, by convention, parameter names in Rust are short, often just a letter, and Rust’s type-naming convention is CamelCase. Short for “type,” `T` is the default choice of most Rust programmers. @@ -63,8 +64,8 @@ compile yet, but we’ll fix it later in this chapter. {{#rustdoc_include ../listings/ch10-generic-types-traits-and-lifetimes/listing-10-05/src/main.rs}} ``` -Listing 10-5: A definition of the `largest` function that -uses generic type parameters but doesn’t compile yet +Listing 10-5: The `largest` function using generic type +parameters; this doesn’t yet compile If we compile this code right now, we’ll get this error: @@ -73,21 +74,22 @@ If we compile this code right now, we’ll get this error: ``` The note mentions `std::cmp::PartialOrd`, which is a *trait*. We’ll talk about -traits in the next section. For now, this error states that the body of -`largest` won’t work for all possible types that `T` could be. Because we want -to compare values of type `T` in the body, we can only use types whose values -can be ordered. To enable comparisons, the standard library has the +traits in the next section. For now, know that this error states that the body +of `largest` won’t work for all possible types that `T` could be. Because we +want to compare values of type `T` in the body, we can only use types whose +values can be ordered. To enable comparisons, the standard library has the `std::cmp::PartialOrd` trait that you can implement on types (see Appendix C for more on this trait). You’ll learn how to specify that a generic type has a particular trait in the [“Traits as Parameters”][traits-as-parameters] section, but let’s first explore other ways of using generic type -parameters. +ignore --> section. Before we fix this code (in the section [“Fixing the +`largest` Function with Trait Bounds”][fixing]), let’s first +explore other ways of using generic type parameters. ### In Struct Definitions We can also define structs to use a generic type parameter in one or more -fields using the `<>` syntax. Listing 10-6 shows how to define a `Point` -struct to hold `x` and `y` coordinate values of any type. +fields using the `<>` syntax. Listing 10-6 defines a `Point` struct to hold +`x` and `y` coordinate values of any type. Filename: src/main.rs @@ -100,9 +102,8 @@ values of type `T` The syntax for using generics in struct definitions is similar to that used in function definitions. First, we declare the name of the type parameter inside -angle brackets just after the name of the struct. Then we can use the generic -type in the struct definition where we would otherwise specify concrete data -types. +angle brackets just after the name of the struct. Then we use the generic type +in the struct definition where we would otherwise specify concrete data types. Note that because we’ve used only one generic type to define `Point`, this definition says that the `Point` struct is generic over some type `T`, and @@ -119,8 +120,11 @@ Listing 10-7, our code won’t compile. Listing 10-7: The fields `x` and `y` must be the same type because both have the same generic data type `T`. -In this example, when we assign the integer value 5 to `x`, we let the -compiler know that the generic type `T` will be an integer for this instance of +Listing 10-7: The fields `x` and `y` must be the same type because both have +the same generic data type `T`. + +In this example, when we assign the integer value 5 to `x`, we let the compiler +know that the generic type `T` will be an integer for this instance of `Point`. Then when we specify 4.0 for `y`, which we’ve defined to have the same type as `x`, we’ll get a type mismatch error like this: @@ -130,8 +134,8 @@ same type as `x`, we’ll get a type mismatch error like this: To define a `Point` struct where `x` and `y` are both generics but could have different types, we can use multiple generic type parameters. For example, in -Listing 10-8, we can change the definition of `Point` to be generic over types -`T` and `U` where `x` is of type `T` and `y` is of type `U`. +Listing 10-8, we change the definition of `Point` to be generic over types `T` +and `U` where `x` is of type `T` and `y` is of type `U`. Filename: src/main.rs @@ -144,8 +148,9 @@ that `x` and `y` can be values of different types Now all the instances of `Point` shown are allowed! You can use as many generic type parameters in a definition as you want, but using more than a few makes -your code hard to read. When you need lots of generic types in your code, it -could indicate that your code needs restructuring into smaller pieces. +your code hard to read. If you're finding you need lots of generic types in +your code, it could indicate that your code needs restructuring into smaller +pieces. ### In Enum Definitions @@ -160,10 +165,10 @@ enum Option { } ``` -This definition should now make more sense to you. As you can see, `Option` -is an enum that is generic over type `T` and has two variants: `Some`, which +This definition should now make more sense to you. As you can see, the +`Option` enum is generic over type `T` and has two variants: `Some`, which holds one value of type `T`, and a `None` variant that doesn’t hold any value. -By using the `Option` enum, we can express the abstract concept of having an +By using the `Option` enum, we can express the abstract concept of an optional value, and because `Option` is generic, we can use this abstraction no matter what the type of the optional value is. @@ -209,21 +214,20 @@ struct we defined in Listing 10-6 with a method named `x` implemented on it. Here, we’ve defined a method named `x` on `Point` that returns a reference to the data in the field `x`. -Note that we have to declare `T` just after `impl` so we can use it to specify +Note that we have to declare `T` just after `impl` so we can use `T` to specify that we’re implementing methods on the type `Point`. By declaring `T` as a generic type after `impl`, Rust can identify that the type in the angle -brackets in `Point` is a generic type rather than a concrete type. Because this -is declaring the generic again, we could have chosen a different name for the -generic parameter than the generic parameter declared in the struct definition, -but using the same name is conventional. Methods written within an `impl` that -declares the generic type will be defined on any instance of the type, no -matter what concrete type ends up substituting for the generic type. +brackets in `Point` is a generic type rather than a concrete type. We could +have chosen a different name for this generic parameter than the generic +parameter declared in the struct definition, but using the same name is +conventional. Methods written within an `impl` that declares the generic type +will be defined on any instance of the type, no matter what concrete type ends +up substituting for the generic type. -The other option we have is defining methods on the type with some constraint -on the generic type. We could, for example, implement methods only on -`Point` instances rather than on `Point` instances with any generic -type. In Listing 10-10 we use the concrete type `f32`, meaning we don’t declare -any types after `impl`. +We can also specify constraints on generic types when defining methods on the +type. We could, for example, implement methods only on `Point` instances +rather than on `Point` instances with any generic type. In Listing 10-10 we +use the concrete type `f32`, meaning we don’t declare any types after `impl`. Filename: src/main.rs @@ -234,14 +238,14 @@ any types after `impl`. Listing 10-10: An `impl` block that only applies to a struct with a particular concrete type for the generic type parameter `T` -This code means the type `Point` will have a method named -`distance_from_origin` and other instances of `Point` where `T` is not of -type `f32` will not have this method defined. The method measures how far our -point is from the point at coordinates (0.0, 0.0) and uses mathematical -operations that are available only for floating point types. +This code means the type `Point` will have a `distance_from_origin` +method; other instances of `Point` where `T` is not of type `f32` will not +have this method defined. The method measures how far our point is from the +point at coordinates (0.0, 0.0) and uses mathematical operations that are +available only for floating point types. Generic type parameters in a struct definition aren’t always the same as those -you use in that struct’s method signatures. Listing 10-11 uses the generic +you use in that same struct’s method signatures. Listing 10-11 uses the generic types `X1` and `Y1` for the `Point` struct and `X2` `Y2` for the `mixup` method signature to make the example clearer. The method creates a new `Point` instance with the `x` value from the `self` `Point` (of type `X1`) and the `y` @@ -253,7 +257,7 @@ value from the passed-in `Point` (of type `Y2`). {{#rustdoc_include ../listings/ch10-generic-types-traits-and-lifetimes/listing-10-11/src/main.rs}} ``` -Listing 10-11: A method that uses different generic types +Listing 10-11: A method that uses generic types different from its struct’s definition In `main`, we’ve defined a `Point` that has an `i32` for `x` (with value `5`) @@ -273,22 +277,19 @@ method. ### Performance of Code Using Generics -You might be wondering whether there is a runtime cost when you’re using -generic type parameters. The good news is that Rust implements generics in such -a way that your code doesn’t run any slower using generic types than it would -with concrete types. +You might be wondering whether there is a runtime cost when using generic type +parameters. The good news is that using generic types won't make your run any +slower than it would with concrete types. -Rust accomplishes this by performing monomorphization of the code that is using +Rust accomplishes this by performing monomorphization of the code using generics at compile time. *Monomorphization* is the process of turning generic code into specific code by filling in the concrete types that are used when -compiled. +compiled. In this process, the compiler does the opposite of the steps we used +to create the generic function in Listing 10-5: the compiler looks at all the +places where generic code is called and generates code for the concrete types +the generic code is called with. -In this process, the compiler does the opposite of the steps we used to create -the generic function in Listing 10-5: the compiler looks at all the places -where generic code is called and generates code for the concrete types the -generic code is called with. - -Let’s look at how this works with an example that uses the standard library’s +Let’s look at how this works by using the standard library’s generic `Option` enum: ```rust @@ -303,8 +304,7 @@ is `f64`. As such, it expands the generic definition of `Option` into `Option_i32` and `Option_f64`, thereby replacing the generic definition with the specific ones. -The monomorphized version of the code looks like the following. The generic -`Option` is replaced with the specific definitions created by the compiler: +The monomorphized version of the code looks like the following: Filename: src/main.rs @@ -325,10 +325,12 @@ fn main() { } ``` -Because Rust compiles generic code into code that specifies the type in each -instance, we pay no runtime cost for using generics. When the code runs, it -performs just as it would if we had duplicated each definition by hand. The -process of monomorphization makes Rust’s generics extremely efficient at -runtime. +The generic `Option` is replaced with the specific definitions created by +the compiler. Because Rust compiles generic code into code that specifies the +type in each instance, we pay no runtime cost for using generics. When the code +runs, it performs just as it would if we had duplicated each definition by +hand. The process of monomorphization makes Rust’s generics extremely efficient +at runtime. [traits-as-parameters]: ch10-02-traits.html#traits-as-parameters +[fixing]: ch10-02-traits.html#fixing-the-largest-function-with-trait-bounds diff --git a/src/ch10-02-traits.md b/src/ch10-02-traits.md index 98dedde14..d93af71ae 100644 --- a/src/ch10-02-traits.md +++ b/src/ch10-02-traits.md @@ -1,9 +1,9 @@ ## Traits: Defining Shared Behavior -A *trait* tells the Rust compiler about functionality a particular type has and -can share with other types. We can use traits to define shared behavior in an -abstract way. We can use trait bounds to specify that a generic type can be any -type that has certain behavior. +A *trait* defines functionality a particular type has and can share with other +types. We can use traits to define shared behavior in an abstract way. We can +use *trait bounds* to specify that a generic type can be any type that has +certain behavior. > Note: Traits are similar to a feature often called *interfaces* in other > languages, although with some differences. @@ -72,20 +72,19 @@ already limited to 280 characters. `NewsArticle` and `Tweet` types Implementing a trait on a type is similar to implementing regular methods. The -difference is that after `impl`, we put the trait name that we want to -implement, then use the `for` keyword, and then specify the name of the type we -want to implement the trait for. Within the `impl` block, we put the method -signatures that the trait definition has defined. Instead of adding a semicolon -after each signature, we use curly brackets and fill in the method body with -the specific behavior that we want the methods of the trait to have for the -particular type. +difference is that after `impl`, we put the trait name we want to implement, +then use the `for` keyword, and then specify the name of the type we want to +implement the trait for. Within the `impl` block, we put the method signatures +that the trait definition has defined. Instead of adding a semicolon after each +signature, we use curly brackets and fill in the method body with the specific +behavior that we want the methods of the trait to have for the particular type. Now that the library has implemented the `Summary` trait on `NewsArticle` and `Tweet`, users of the crate can call the trait methods on instances of `NewsArticle` and `Tweet` in the same way we call regular methods. The only -difference is that the trait has to be brought into scope as well as the types -to get the additional trait methods. Here’s an example of how a binary crate -could use our `aggregator` library crate: +difference is that the user must bring the trait into scope as well as the +types. Here’s an example of how a binary crate could use our `aggregator` +library crate: ```rust,ignore {{#rustdoc_include ../listings/ch10-generic-types-traits-and-lifetimes/no-listing-01-calling-trait-method/src/main.rs}} @@ -95,22 +94,22 @@ This code prints `1 new tweet: horse_ebooks: of course, as you probably already know, people`. Other crates that depend on the `aggregator` crate can also bring the `Summary` -trait into scope to implement the trait on their own types. One restriction to -note with trait implementations is that we can implement a trait on a type only -if at least one of the trait or the type is local to our crate. For example, we -can implement standard library traits like `Display` on a custom type like -`Tweet` as part of our `aggregator` crate functionality, because the type -`Tweet` is local to our `aggregator` crate. We can also implement `Summary` on -`Vec` in our `aggregator` crate, because the trait `Summary` is local to our -`aggregator` crate. +trait into scope to implement `Summary` on their own types. One restriction to +note is that we can implement a trait on a type only if at least one of the +trait or the type is local to our crate. For example, we can implement standard +library traits like `Display` on a custom type like `Tweet` as part of our +`aggregator` crate functionality, because the type `Tweet` is local to our +`aggregator` crate. We can also implement `Summary` on `Vec` in our +`aggregator` crate, because the trait `Summary` is local to our `aggregator` +crate. But we can’t implement external traits on external types. For example, we can’t implement the `Display` trait on `Vec` within our `aggregator` crate, -because `Display` and `Vec` are defined in the standard library and aren’t -local to our `aggregator` crate. This restriction is part of a property of -programs called *coherence*, and more specifically the *orphan rule*, so named -because the parent type is not present. This rule ensures that other people’s -code can’t break your code and vice versa. Without the rule, two crates could +because `Display` and `Vec` are both defined in the standard library and +aren’t local to our `aggregator` crate. This restriction is part of a property +called *coherence*, and more specifically the *orphan rule*, so named because +the parent type is not present. This rule ensures that other people’s code +can’t break your code and vice versa. Without the rule, two crates could implement the same trait for the same type, and Rust wouldn’t know which implementation to use. @@ -121,9 +120,9 @@ in a trait instead of requiring implementations for all methods on every type. Then, as we implement the trait on a particular type, we can keep or override each method’s default behavior. -Listing 10-14 shows how to specify a default string for the `summarize` method -of the `Summary` trait instead of only defining the method signature, as we did -in Listing 10-12. +In Listing 10-14 we specify a default string for the `summarize` method of the +`Summary` trait instead of only defining the method signature, as we did in +Listing 10-12. Filename: src/lib.rs @@ -131,12 +130,11 @@ in Listing 10-12. {{#rustdoc_include ../listings/ch10-generic-types-traits-and-lifetimes/listing-10-14/src/lib.rs:here}} ``` -Listing 10-14: Definition of a `Summary` trait with a -default implementation of the `summarize` method +Listing 10-14: Defining a `Summary` trait with a default +implementation of the `summarize` method -To use a default implementation to summarize instances of `NewsArticle` instead -of defining a custom implementation, we specify an empty `impl` block with -`impl Summary for NewsArticle {}`. +To use a default implementation to summarize instances of `NewsArticle`, we +specify an empty `impl` block with `impl Summary for NewsArticle {}`. Even though we’re no longer defining the `summarize` method on `NewsArticle` directly, we’ve provided a default implementation and specified that @@ -149,11 +147,10 @@ the `summarize` method on an instance of `NewsArticle`, like this: This code prints `New article available! (Read more...)`. -Creating a default implementation for `summarize` doesn’t require us to change -anything about the implementation of `Summary` on `Tweet` in Listing 10-13. The -reason is that the syntax for overriding a default implementation is the same -as the syntax for implementing a trait method that doesn’t have a default -implementation. +Creating a default implementation doesn’t require us to change anything about +the implementation of `Summary` on `Tweet` in Listing 10-13. The reason is that +the syntax for overriding a default implementation is the same as the syntax +for implementing a trait method that doesn’t have a default implementation. Default implementations can call other methods in the same trait, even if those other methods don’t have a default implementation. In this way, a trait can @@ -192,13 +189,11 @@ overriding implementation of that same method. ### Traits as Parameters Now that you know how to define and implement traits, we can explore how to use -traits to define functions that accept many different types. - -For example, in Listing 10-13, we implemented the `Summary` trait on the -`NewsArticle` and `Tweet` types. We can define a `notify` function that calls -the `summarize` method on its `item` parameter, which is of some type that -implements the `Summary` trait. To do this, we can use the `impl Trait` -syntax, like this: +traits to define functions that accept many different types. We'll use the +`Summary` trait we implemented on the `NewsArticle` and `Tweet` types in +Listing 10-13 to define a `notify` function that calls the `summarize` method +on its `item` parameter, which is of some type that implements the `Summary` +trait. To do this, we use the `impl Trait` syntax, like this: ```rust,ignore {{#rustdoc_include ../listings/ch10-generic-types-traits-and-lifetimes/no-listing-04-traits-as-parameters/src/lib.rs:here}} @@ -214,9 +209,8 @@ because those types don’t implement `Summary`. #### Trait Bound Syntax -The `impl Trait` syntax works for straightforward cases but is actually -syntax sugar for a longer form, which is called a *trait bound*; it looks like -this: +The `impl Trait` syntax works for straightforward cases but is actually syntax +sugar for a longer form known as a *trait bound*; it looks like this: ```rust,ignore pub fn notify(item: &T) { @@ -229,18 +223,18 @@ more verbose. We place trait bounds with the declaration of the generic type parameter after a colon and inside angle brackets. The `impl Trait` syntax is convenient and makes for more concise code in simple -cases. The trait bound syntax can express more complexity in other cases. For -example, we can have two parameters that implement `Summary`. Using the `impl -Trait` syntax looks like this: +cases, while the fuller trait bound syntax can express more complexity in other +cases. For example, we can have two parameters that implement `Summary`. Doing +so with the `impl Trait` syntax looks like this: ```rust,ignore pub fn notify(item1: &impl Summary, item2: &impl Summary) { ``` -If we wanted this function to allow `item1` and `item2` to have different -types, using `impl Trait` would be appropriate (as long as both types implement -`Summary`). If we wanted to force both parameters to have the same type, that’s -only possible to express using a trait bound, like this: +Using `impl Trait` is appropriate if we want this function to allow `item1` and +`item2` to have different types (as long as both types implement `Summary`). If +we want to force both parameters to have the same type, however, we must use a +trait bound, like this: ```rust,ignore pub fn notify(item1: &T, item2: &T) { @@ -253,9 +247,9 @@ passed as an argument for `item1` and `item2` must be the same. #### Specifying Multiple Trait Bounds with the `+` Syntax We can also specify more than one trait bound. Say we wanted `notify` to use -display formatting on `item` as well as the `summarize` method: we specify in -the `notify` definition that `item` must implement both `Display` and -`Summary`. We can do so using the `+` syntax: +display formatting as well as `summarize` on `item`: we specify in the `notify` +definition that `item` must implement both `Display` and `Summary`. We can do +so using the `+` syntax: ```rust,ignore pub fn notify(item: &(impl Summary + Display)) { @@ -308,14 +302,14 @@ value of some type that implements a trait, as shown here: By using `impl Summary` for the return type, we specify that the `returns_summarizable` function returns some type that implements the `Summary` trait without naming the concrete type. In this case, `returns_summarizable` -returns a `Tweet`, but the code calling this function doesn’t know that. +returns a `Tweet`, but the code calling this function doesn’t need to know that. -The ability to return a type that is only specified by the trait it implements -is especially useful in the context of closures and iterators, which we cover -in Chapter 13. Closures and iterators create types that only the compiler knows -or types that are very long to specify. The `impl Trait` syntax lets you -concisely specify that a function returns some type that implements the -`Iterator` trait without needing to write out a very long type. +The ability to specify a return type only by the trait it implements is +especially useful in the context of closures and iterators, which we cover in +Chapter 13. Closures and iterators create types that only the compiler knows or +types that are very long to specify. The `impl Trait` syntax lets you concisely +specify that a function returns some type that implements the `Iterator` trait +without needing to write out a very long type. However, you can only use `impl Trait` if you’re returning a single type. For example, this code that returns either a `NewsArticle` or a `Tweet` with the @@ -334,10 +328,10 @@ ignore --> section of Chapter 17. ### Fixing the `largest` Function with Trait Bounds -Now that you know how to specify the behavior you want to use using the generic -type parameter’s bounds, let’s return to Listing 10-5 to fix the definition of -the `largest` function that uses a generic type parameter! Last time we tried -to run that code, we received this error: +Now that you know how to specify the behavior you want using the generic type +parameter’s bounds, let’s return to Listing 10-5 to fix the definition of the +`largest` function that uses a generic type parameter! Last time we tried to +run that code, we received this error: ```console {{#include ../listings/ch10-generic-types-traits-and-lifetimes/listing-10-05/output.txt}} @@ -366,11 +360,10 @@ With our non-generic versions of the `largest` function, we were only trying to find the largest `i32` or `char`. As discussed in the [“Stack-Only Data: Copy”][stack-only-data-copy] section in Chapter 4, types like `i32` and `char` that have a known size can be stored on the stack, so they -implement the `Copy` trait. But when we made the `largest` function generic, -it became possible for the `list` parameter to have types in it that don’t -implement the `Copy` trait. Consequently, we wouldn’t be able to move the -value out of `list[0]` and into the `largest` variable, resulting in this -error. +implement the `Copy` trait. But when we made the `largest` function generic, it +became possible for the `list` parameter to have types in it that don’t +implement the `Copy` trait. Consequently, we wouldn’t be able to move the value +out of `list[0]` and into the `largest` variable, resulting in this error. To call this code with only those types that implement the `Copy` trait, we can add `Copy` to the trait bounds of `T`! Listing 10-15 shows the complete code of @@ -396,14 +389,14 @@ potentially making more heap allocations in the case of types that own heap data like `String`, and heap allocations can be slow if we’re working with large amounts of data. -Another way we could implement `largest` is for the function to return a -reference to a `T` value in the slice. If we change the return type to `&T` -instead of `T`, thereby changing the body of the function to return a -reference, we wouldn’t need the `Clone` or `Copy` trait bounds and we could -avoid heap allocations. Try implementing these alternate solutions on your own! -If you get stuck with errors having to do with lifetimes, keep reading: the -“Validating References with Lifetimes” section coming up will explain, but -lifetimes aren’t required to solve these challenges. +We could also implement `largest` by having the function return a reference to +a `T` value in the slice. If we change the return type to `&T` instead of `T`, +thereby changing the body of the function to return a reference, we wouldn’t +need the `Clone` or `Copy` trait bounds and we could avoid heap allocations. +Try implementing these alternate solutions on your own! If you get stuck with +errors having to do with lifetimes, keep reading: the “Validating References +with Lifetimes” section coming up will explain, but lifetimes aren’t required +to solve these challenges. ### Using Trait Bounds to Conditionally Implement Methods @@ -411,7 +404,7 @@ By using a trait bound with an `impl` block that uses generic type parameters, we can implement methods conditionally for types that implement the specified traits. For example, the type `Pair` in Listing 10-16 always implements the `new` function to return a new instance of `Pair` (recall from the -[”Defining Methods”][methods] section of Chapter 5 that `Self` +[“Defining Methods”][methods] section of Chapter 5 that `Self` is a type alias for the type of the `impl` block, which in this case is `Pair`). But in the next `impl` block, `Pair` only implements the `cmp_display` method if its inner type `T` implements the `PartialOrd` trait @@ -463,13 +456,6 @@ checks for behavior at runtime because we’ve already checked at compile time. Doing so improves performance without having to give up the flexibility of generics. -Another kind of generic that we’ve already been using is called *lifetimes*. -Rather than ensuring that a type has the behavior we want, lifetimes ensure -that references are valid as long as we need them to be. Let’s look at how -lifetimes do that. - -[stack-only-data-copy]: -ch04-01-what-is-ownership.html#stack-only-data-copy -[using-trait-objects-that-allow-for-values-of-different-types]: -ch17-02-trait-objects.html#using-trait-objects-that-allow-for-values-of-different-types +[stack-only-data-copy]: ch04-01-what-is-ownership.html#stack-only-data-copy +[using-trait-objects-that-allow-for-values-of-different-types]: ch17-02-trait-objects.html#using-trait-objects-that-allow-for-values-of-different-types [methods]: ch05-03-method-syntax.html#defining-methods diff --git a/src/ch10-03-lifetime-syntax.md b/src/ch10-03-lifetime-syntax.md index 1746ca1fd..191f24306 100644 --- a/src/ch10-03-lifetime-syntax.md +++ b/src/ch10-03-lifetime-syntax.md @@ -1,24 +1,27 @@ ## Validating References with Lifetimes +Lifetimes are another kind of generic that we’ve already been using. Rather +than ensuring that a type has the behavior we want, lifetimes ensure that +references are valid as long as we need them to be. + One detail we didn’t discuss in the [“References and Borrowing”][references-and-borrowing] section in Chapter 4 is that every reference in Rust has a *lifetime*, which is the scope for which -that reference is valid. Most of the time, lifetimes are implicit and -inferred, just like most of the time, types are inferred. We must annotate -types when multiple types are possible. In a similar way, we must annotate -lifetimes when the lifetimes of references could be related in a few different -ways. Rust requires us to annotate the relationships using generic lifetime -parameters to ensure the actual references used at runtime will definitely be -valid. +that reference is valid. Most of the time, lifetimes are implicit and inferred, +just like most of the time, types are inferred. We only must annotate types +when multiple types are possible. In a similar way, we must annotate lifetimes +when the lifetimes of references could be related in a few different ways. Rust +requires us to annotate the relationships using generic lifetime parameters to +ensure the actual references used at runtime will definitely be valid. Annotating lifetimes is not even a concept most other programming languages have, so this is going to feel unfamiliar. Although we won’t cover lifetimes in their entirety in this chapter, we’ll discuss common ways you might encounter -lifetime syntax so you can get introduced to the concept. +lifetime syntax so you can get comfortable with the concept. ### Preventing Dangling References with Lifetimes -The main aim of lifetimes is to prevent dangling references, which cause a +The main aim of lifetimes is to prevent *dangling references*, which cause a program to reference data other than the data it’s intended to reference. Consider the program in Listing 10-17, which has an outer scope and an inner scope. @@ -96,10 +99,10 @@ lifetimes of parameters and return values in the context of functions. ### Generic Lifetimes in Functions -Let’s write a function that returns the longer of two string slices. This -function will take two string slices and return a string slice. After we’ve -implemented the `longest` function, the code in Listing 10-20 should print `The -longest string is abcd`. +We’ll write a function that returns the longer of two string slices. This +function will take two string slices and return a single string slice. After +we’ve implemented the `longest` function, the code in Listing 10-20 should +print `The longest string is abcd`. Filename: src/main.rs @@ -111,8 +114,8 @@ longest string is abcd`. function to find the longer of two string slices Note that we want the function to take string slices, which are references, -because we don’t want the `longest` function to take ownership of its -parameters. Refer to the [“String Slices as +rather than strings, because we don’t want the `longest` function to take +ownership of its parameters. Refer to the [“String Slices as Parameters”][string-slices-as-parameters] section in Chapter 4 for more discussion about why the parameters we use in Listing 10-20 are the ones we want. @@ -155,18 +158,17 @@ perform its analysis. ### Lifetime Annotation Syntax -Lifetime annotations don’t change how long any of the references live. Just -as functions can accept any type when the signature specifies a generic type -parameter, functions can accept references with any lifetime by specifying a -generic lifetime parameter. Lifetime annotations describe the relationships of -the lifetimes of multiple references to each other without affecting the -lifetimes. +Lifetime annotations don’t change how long any of the references live. Rather, +they describe the relationships of the lifetimes of multiple references to each +other without affecting the lifetimes. Just as functions can accept any type +when the signature specifies a generic type parameter, functions can accept +references with any lifetime by specifying a generic lifetime parameter. Lifetime annotations have a slightly unusual syntax: the names of lifetime -parameters must start with an apostrophe (`'`) and are usually all lowercase and -very short, like generic types. Most people use the name `'a`. We place -lifetime parameter annotations after the `&` of a reference, using a space to -separate the annotation from the reference’s type. +parameters must start with an apostrophe (`'`) and are usually all lowercase +and very short, like generic types. Most people use the name `'a` for the first +lifetime annotation. We place lifetime parameter annotations after the `&` of a +reference, using a space to separate the annotation from the reference’s type. Here are some examples: a reference to an `i32` without a lifetime parameter, a reference to an `i32` that has a lifetime parameter named `'a`, and a mutable @@ -192,11 +194,11 @@ lifetime. Now let’s examine lifetime annotations in the context of the `longest` function. As with generic type parameters, we need to declare generic lifetime parameters inside angle brackets between the function name and the parameter -list. The constraint we want to express in this signature is that the lifetimes -of both of the parameters and the lifetime of the returned reference are -related such that the returned reference will be valid as long as both the -parameters are. We’ll name the lifetime `'a` and then add it to each reference, -as shown in Listing 10-22. +list. We want the signature to express the following constraint: the returned +reference will be valid as long as both the parameters are valid. This is the +relationship between lifetimes of the parameters and the return value. We’ll +name the lifetime `'a` and then add it to each reference, as shown in Listing +10-22. Filename: src/main.rs @@ -229,7 +231,7 @@ substituted for `'a` that will satisfy this signature. When annotating lifetimes in functions, the annotations go in the function signature, not in the function body. The lifetime annotations become part of -the contract of the function, much like the types in the signature are. Having +the contract of the function, much like the types in the signature. Having function signatures contain the lifetime contract means the analysis the Rust compiler does can be simpler. If there’s a problem with the way a function is annotated or the way it is called, the compiler errors can point to the part of @@ -262,16 +264,16 @@ references to `String` values that have different concrete lifetimes In this example, `string1` is valid until the end of the outer scope, `string2` is valid until the end of the inner scope, and `result` references something that is valid until the end of the inner scope. Run this code, and you’ll see -that the borrow checker approves of this code; it will compile and print `The -longest string is long string is long`. +that the borrow checker approves; it will compile and print `The longest string +is long string is long`. Next, let’s try an example that shows that the lifetime of the reference in `result` must be the smaller lifetime of the two arguments. We’ll move the declaration of the `result` variable outside the inner scope but leave the assignment of the value to the `result` variable inside the scope with -`string2`. Then we’ll move the `println!` that uses `result` outside the inner -scope, after the inner scope has ended. The code in Listing 10-24 will not -compile. +`string2`. Then we’ll move the `println!` that uses `result` to outside the +inner scope, after the inner scope has ended. The code in Listing 10-24 will +not compile. Filename: src/main.rs @@ -282,7 +284,7 @@ compile. Listing 10-24: Attempting to use `result` after `string2` has gone out of scope -When we try to compile this code, we’ll get this error: +When we try to compile this code, we get this error: ```console {{#include ../listings/ch10-generic-types-traits-and-lifetimes/listing-10-24/output.txt}} @@ -321,16 +323,17 @@ following code will compile: {{#rustdoc_include ../listings/ch10-generic-types-traits-and-lifetimes/no-listing-08-only-one-reference-with-lifetime/src/main.rs:here}} ``` -In this example, we’ve specified a lifetime parameter `'a` for the parameter -`x` and the return type, but not for the parameter `y`, because the lifetime of -`y` does not have any relationship with the lifetime of `x` or the return value. +We’ve specified a lifetime parameter `'a` for the parameter `x` and the return +type, but not for the parameter `y`, because the lifetime of `y` does not have +any relationship with the lifetime of `x` or the return value. When returning a reference from a function, the lifetime parameter for the return type needs to match the lifetime parameter for one of the parameters. If the reference returned does *not* refer to one of the parameters, it must refer -to a value created within this function, which would be a dangling reference -because the value will go out of scope at the end of the function. Consider -this attempted implementation of the `longest` function that won’t compile: +to a value created within this function. However, this would be a dangling +reference because the value will go out of scope at the end of the function. +Consider this attempted implementation of the `longest` function that won’t +compile: Filename: src/main.rs @@ -362,10 +365,10 @@ would create dangling pointers or otherwise violate memory safety. ### Lifetime Annotations in Struct Definitions -So far, we’ve only defined structs to hold owned types. It’s possible for -structs to hold references, but in that case we would need to add a lifetime -annotation on every reference in the struct’s definition. Listing 10-25 has a -struct named `ImportantExcerpt` that holds a string slice. +So far, the structs we've define all hold owned types. We can define structs to +hold references, but in that case we would need to add a lifetime annotation on +every reference in the struct’s definition. Listing 10-25 has a struct named +`ImportantExcerpt` that holds a string slice. Filename: src/main.rs @@ -394,8 +397,8 @@ the `ImportantExcerpt` goes out of scope, so the reference in the You’ve learned that every reference has a lifetime and that you need to specify lifetime parameters for functions or structs that use references. However, in -Chapter 4 we had a function in Listing 4-9, which is shown again in Listing -10-26, that compiled without lifetime annotations. +Chapter 4 we had a function in Listing 4-9, shown again in Listing 10-26, that +compiled without lifetime annotations. Filename: src/lib.rs @@ -435,39 +438,38 @@ fits these cases, you don’t need to write the lifetimes explicitly. The elision rules don’t provide full inference. If Rust deterministically applies the rules but there is still ambiguity as to what lifetimes the references have, the compiler won’t guess what the lifetime of the remaining -references should be. In this case, instead of guessing, the compiler will give -you an error that you can resolve by adding the lifetime annotations that -specify how the references relate to each other. +references should be. Instead of guessing, the compiler will give you an error +that you can resolve by adding the lifetime annotations. Lifetimes on function or method parameters are called *input lifetimes*, and lifetimes on return values are called *output lifetimes*. -The compiler uses three rules to figure out what lifetimes references have when -there aren’t explicit annotations. The first rule applies to input lifetimes, -and the second and third rules apply to output lifetimes. If the compiler gets -to the end of the three rules and there are still references for which it can’t -figure out lifetimes, the compiler will stop with an error. These rules apply -to `fn` definitions as well as `impl` blocks. +The compiler uses three rules to figure out the lifetimes of the references +when there aren’t explicit annotations. The first rule applies to input +lifetimes, and the second and third rules apply to output lifetimes. If the +compiler gets to the end of the three rules and there are still references for +which it can’t figure out lifetimes, the compiler will stop with an error. +These rules apply to `fn` definitions as well as `impl` blocks. -The first rule is that each parameter that is a reference gets its own lifetime -parameter. In other words, a function with one parameter gets one lifetime -parameter: `fn foo<'a>(x: &'a i32)`; a function with two parameters gets two -separate lifetime parameters: `fn foo<'a, 'b>(x: &'a i32, y: &'b i32)`; and so -on. +The first rule is that the compiler assigns a lifetime parameter to each +parameter that’s a reference. In other words, a function with one parameter +gets one lifetime parameter: `fn foo<'a>(x: &'a i32)`; a function with two +parameters gets two separate lifetime parameters: `fn foo<'a, 'b>(x: &'a i32, +y: &'b i32)`; and so on. -The second rule is if there is exactly one input lifetime parameter, that +The second rule is that, if there is exactly one input lifetime parameter, that lifetime is assigned to all output lifetime parameters: `fn foo<'a>(x: &'a i32) -> &'a i32`. -The third rule is if there are multiple input lifetime parameters, but one of -them is `&self` or `&mut self` because this is a method, the lifetime of `self` -is assigned to all output lifetime parameters. This third rule makes methods -much nicer to read and write because fewer symbols are necessary. +The third rule is that, if there are multiple input lifetime parameters, but +one of them is `&self` or `&mut self` because this is a method, the lifetime of +`self` is assigned to all output lifetime parameters. This third rule makes +methods much nicer to read and write because fewer symbols are necessary. -Let’s pretend we’re the compiler. We’ll apply these rules to figure out what -the lifetimes of the references in the signature of the `first_word` function -in Listing 10-26 are. The signature starts without any lifetimes associated -with the references: +Let’s pretend we’re the compiler. We’ll apply these rules to figure out the +lifetimes of the references in the signature of the `first_word` function in +Listing 10-26. The signature starts without any lifetimes associated with the +references: ```rust,ignore fn first_word(s: &str) -> &str { @@ -560,9 +562,9 @@ and all lifetimes have been accounted for. ### The Static Lifetime -One special lifetime we need to discuss is `'static`, which means that this -reference *can* live for the entire duration of the program. All string -literals have the `'static` lifetime, which we can annotate as follows: +One special lifetime we need to discuss is `'static`, which denotes that the +affected reference *can* live for the entire duration of the program. All +string literals have the `'static` lifetime, which we can annotate as follows: ```rust let s: &'static str = "I have a static lifetime."; @@ -575,10 +577,10 @@ is always available. Therefore, the lifetime of all string literals is You might see suggestions to use the `'static` lifetime in error messages. But before specifying `'static` as the lifetime for a reference, think about whether the reference you have actually lives the entire lifetime of your -program or not. You might consider whether you want it to live that long, even -if it could. Most of the time, the problem results from attempting to create a -dangling reference or a mismatch of the available lifetimes. In such cases, the -solution is fixing those problems, not specifying the `'static` lifetime. +program or not, and whether you want it to. Most of the time, an error message +suggesting the `'static` lifetime results from attempting to create a dangling +reference or a mismatch of the available lifetimes. In such cases, the solution +is fixing those problems, not specifying the `'static` lifetime. ## Generic Type Parameters, Trait Bounds, and Lifetimes Together