From 4bfc9edbc4b5734b118f6fcee4bd86bf0dba5daa Mon Sep 17 00:00:00 2001 From: "Carol (Nichols || Goulding)" Date: Wed, 30 Mar 2022 21:15:25 -0400 Subject: [PATCH 01/10] Use impl Iterator instead of std::env::Args Fixes #2021. Fixes #3052. --- .../listing-13-26/src/lib.rs | 4 ++- .../listing-13-27/src/lib.rs | 4 ++- .../listing-13-29/src/lib.rs | 4 ++- src/ch13-03-improving-our-io-project.md | 27 ++++++++++++------- 4 files changed, 27 insertions(+), 12 deletions(-) diff --git a/listings/ch13-functional-features/listing-13-26/src/lib.rs b/listings/ch13-functional-features/listing-13-26/src/lib.rs index 2cb0bea7d..5127c0a6f 100644 --- a/listings/ch13-functional-features/listing-13-26/src/lib.rs +++ b/listings/ch13-functional-features/listing-13-26/src/lib.rs @@ -10,7 +10,9 @@ pub struct Config { // ANCHOR: here impl Config { - pub fn new(mut args: env::Args) -> Result { + pub fn new( + mut args: impl Iterator, + ) -> Result { // --snip-- // ANCHOR_END: here if args.len() < 3 { diff --git a/listings/ch13-functional-features/listing-13-27/src/lib.rs b/listings/ch13-functional-features/listing-13-27/src/lib.rs index 7a3356546..8ffad682f 100644 --- a/listings/ch13-functional-features/listing-13-27/src/lib.rs +++ b/listings/ch13-functional-features/listing-13-27/src/lib.rs @@ -10,7 +10,9 @@ pub struct Config { // ANCHOR: here impl Config { - pub fn new(mut args: env::Args) -> Result { + pub fn new( + mut args: impl Iterator, + ) -> Result { args.next(); let query = match args.next() { diff --git a/listings/ch13-functional-features/listing-13-29/src/lib.rs b/listings/ch13-functional-features/listing-13-29/src/lib.rs index bc8a77ef6..3ba2cdeec 100644 --- a/listings/ch13-functional-features/listing-13-29/src/lib.rs +++ b/listings/ch13-functional-features/listing-13-29/src/lib.rs @@ -9,7 +9,9 @@ pub struct Config { } impl Config { - pub fn new(mut args: std::env::Args) -> Result { + pub fn new( + mut args: impl Iterator, + ) -> Result { args.next(); let query = match args.next() { diff --git a/src/ch13-03-improving-our-io-project.md b/src/ch13-03-improving-our-io-project.md index c9e29dbe6..104fcb195 100644 --- a/src/ch13-03-improving-our-io-project.md +++ b/src/ch13-03-improving-our-io-project.md @@ -83,18 +83,25 @@ body. expect an iterator The standard library documentation for the `env::args` function shows that the -type of the iterator it returns is `std::env::Args`. We’ve updated the -signature of the `Config::new` function so the parameter `args` has the type -`std::env::Args` instead of `&[String]`. Because we’re taking ownership of -`args` and we’ll be mutating `args` by iterating over it, we can add the `mut` -keyword into the specification of the `args` parameter to make it mutable. +type of the iterator it returns is `std::env::Args`, and that type implements +the `Iterator` trait and returns `String` values. + +We’ve updated the signature of the `Config::new` function so the parameter +`args` has a generic type with the trait bounds `impl Iterator` +instead of `&[String]`. This usage of the `impl Trait` syntax we discussed in +the [“Traits as Parameters”][impl-trait] section of Chapter 10 +means that `args` can be any type that implements the `Iterator` type and +returns `String` items. + +Because we’re taking ownership of `args` and we’ll be mutating `args` by +iterating over it, we can add the `mut` keyword into the specification of the +`args` parameter to make it mutable. #### Using `Iterator` Trait Methods Instead of Indexing -Next, we’ll fix the body of `Config::new`. The standard library documentation -also mentions that `std::env::Args` implements the `Iterator` trait, so we know -we can call the `next` method on it! Listing 13-27 updates the code from -Listing 12-23 to use the `next` method: +Next, we’ll fix the body of `Config::new`. Because `args` implements the +`Iterator` trait, we know we can call the `next` method on it! Listing 13-27 +updates the code from Listing 12-23 to use the `next` method: Filename: src/lib.rs @@ -165,3 +172,5 @@ the iterator must pass. But are the two implementations truly equivalent? The intuitive assumption might be that the more low-level loop will be faster. Let’s talk about performance. + +[impl-trait]: ch10-02-traits.html#traits-as-parameters From f2b9c198319464ff010cf3315a7111ca747104a4 Mon Sep 17 00:00:00 2001 From: "Carol (Nichols || Goulding)" Date: Mon, 4 Apr 2022 19:53:56 -0400 Subject: [PATCH 02/10] Add more weasel words around closure type annotation. Fixes #2837. --- src/ch13-01-closures.md | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/src/ch13-01-closures.md b/src/ch13-01-closures.md index 26e0075c5..2ad7b54d8 100644 --- a/src/ch13-01-closures.md +++ b/src/ch13-01-closures.md @@ -201,22 +201,19 @@ involved with closures. ### Closure Type Inference and Annotation -Closures don’t require you to annotate the types of the parameters or the -return value like `fn` functions do. Type annotations are required on functions -because they’re part of an explicit interface exposed to your users. Defining -this interface rigidly is important for ensuring that everyone agrees on what -types of values a function uses and returns. But closures aren’t used in an -exposed interface like this: they’re stored in variables and used without +Closures don’t usually require you to annotate the types of the parameters or +the return value like `fn` functions do. Type annotations are required on +functions because they’re part of an explicit interface exposed to your users. +Defining this interface rigidly is important for ensuring that everyone agrees +on what types of values a function uses and returns. But closures aren’t used +in an exposed interface like this: they’re stored in variables and used without naming them and exposing them to users of our library. -Closures are usually short and relevant only within a narrow context rather -than in any arbitrary scenario. Within these limited contexts, the compiler is -reliably able to infer the types of the parameters and the return type, similar -to how it’s able to infer the types of most variables. - -Making programmers annotate the types in these small, anonymous functions would -be annoying and largely redundant with the information the compiler already has -available. +Closures are typically short and relevant only within a narrow context rather +than in any arbitrary scenario. Within these limited contexts, the compiler can +infer the types of the parameters and the return type, similar to how it’s able +to infer the types of most variables (there are rare cases where the compiler +needs closure type annotations too). As with variables, we can add type annotations if we want to increase explicitness and clarity at the cost of being more verbose than is strictly From ce4da5ccbfc13105b444e8528a3603157773ffa7 Mon Sep 17 00:00:00 2001 From: "Carol (Nichols || Goulding)" Date: Tue, 5 Apr 2022 20:50:33 -0400 Subject: [PATCH 03/10] Start of a new closure example --- ci/dictionary.txt | 1 + .../listing-13-01/Cargo.lock | 7 +- .../listing-13-01/Cargo.toml | 2 +- .../listing-13-01/output.txt | 6 + .../listing-13-01/src/main.rs | 60 +- src/ch13-01-closures.md | 591 +++++------------- src/ch13-02-iterators.md | 146 ----- 7 files changed, 203 insertions(+), 610 deletions(-) create mode 100644 listings/ch13-functional-features/listing-13-01/output.txt diff --git a/ci/dictionary.txt b/ci/dictionary.txt index c0f7848a9..55740898a 100644 --- a/ci/dictionary.txt +++ b/ci/dictionary.txt @@ -429,6 +429,7 @@ SelectBox semver SemVer serde +ShirtColor ShlAssign ShrAssign shouldn diff --git a/listings/ch13-functional-features/listing-13-01/Cargo.lock b/listings/ch13-functional-features/listing-13-01/Cargo.lock index 75ff09e51..6f974d1ba 100644 --- a/listings/ch13-functional-features/listing-13-01/Cargo.lock +++ b/listings/ch13-functional-features/listing-13-01/Cargo.lock @@ -1,6 +1,7 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -[[package]] -name = "workout-app" -version = "0.1.0" +version = 3 +[[package]] +name = "shirt-company" +version = "0.1.0" diff --git a/listings/ch13-functional-features/listing-13-01/Cargo.toml b/listings/ch13-functional-features/listing-13-01/Cargo.toml index f09a737d4..1eb392dfa 100644 --- a/listings/ch13-functional-features/listing-13-01/Cargo.toml +++ b/listings/ch13-functional-features/listing-13-01/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "workout-app" +name = "shirt-company" version = "0.1.0" edition = "2021" diff --git a/listings/ch13-functional-features/listing-13-01/output.txt b/listings/ch13-functional-features/listing-13-01/output.txt new file mode 100644 index 000000000..b64a4d8dc --- /dev/null +++ b/listings/ch13-functional-features/listing-13-01/output.txt @@ -0,0 +1,6 @@ +$ cargo run + Compiling shirt-company v0.1.0 (file:///projects/shirt-company) + Finished dev [unoptimized + debuginfo] target(s) in 0.27s + Running `target/debug/shirt-company` +The user with preference Some(Red) gets Red +The user with preference None gets Blue diff --git a/listings/ch13-functional-features/listing-13-01/src/main.rs b/listings/ch13-functional-features/listing-13-01/src/main.rs index 97eace01d..2c87d6965 100644 --- a/listings/ch13-functional-features/listing-13-01/src/main.rs +++ b/listings/ch13-functional-features/listing-13-01/src/main.rs @@ -1,12 +1,52 @@ -// ANCHOR: here -use std::thread; -use std::time::Duration; - -fn simulated_expensive_calculation(intensity: u32) -> u32 { - println!("calculating slowly..."); - thread::sleep(Duration::from_secs(2)); - intensity +#[derive(Debug, PartialEq, Copy, Clone)] +enum ShirtColor { + Red, + Blue, } -// ANCHOR_END: here -fn main() {} +struct Inventory { + shirts: Vec, +} + +impl Inventory { + fn giveaway(&self, user_preference: Option) -> ShirtColor { + user_preference.unwrap_or_else(|| self.most_stocked()) + } + + fn most_stocked(&self) -> ShirtColor { + let mut num_red = 0; + let mut num_blue = 0; + + for color in &self.shirts { + match color { + ShirtColor::Red => num_red += 1, + ShirtColor::Blue => num_blue += 1, + } + } + if num_red > num_blue { + ShirtColor::Red + } else { + ShirtColor::Blue + } + } +} + +fn main() { + let store = Inventory { + shirts: vec![ShirtColor::Blue, ShirtColor::Red, ShirtColor::Blue], + }; + + let user_pref1 = Some(ShirtColor::Red); + let giveaway1 = store.giveaway(user_pref1); + println!( + "The user with preference {:?} gets {:?}", + user_pref1, giveaway1 + ); + + let user_pref2 = None; + let giveaway2 = store.giveaway(user_pref2); + println!( + "The user with preference {:?} gets {:?}", + user_pref2, giveaway2 + ); +} diff --git a/src/ch13-01-closures.md b/src/ch13-01-closures.md index 2ad7b54d8..e951665dc 100644 --- a/src/ch13-01-closures.md +++ b/src/ch13-01-closures.md @@ -7,207 +7,73 @@ closures can capture values from the scope in which they’re defined. We’ll demonstrate how these closure features allow for code reuse and behavior customization. -### Creating an Abstraction of Behavior with Closures +### Capturing the Environment with Closures -Let’s work on an example of a situation in which it’s useful to store a closure -to be executed later. Along the way, we’ll talk about the syntax of closures, -type inference, and traits. +The first aspect of closures we're going to examine is that closures can +capture values from the environment they're defined in for later use. Here's +the scenario: A t-shirt company gives away a free shirt to someone on their +mailing list every so often. People on the mailing list can optionally add +their favorite color to their profile. If the person chosen to get the free +shirt has their favorite color in their profile, they get that color shirt. If +the person hasn't specified a favorite color, they get the color that the +company currently has the most of. -Consider this hypothetical situation: we work at a startup that’s making an app -to generate custom exercise workout plans. The backend is written in Rust, and -the algorithm that generates the workout plan takes into account many factors, -such as the app user’s age, body mass index, exercise preferences, recent -workouts, and an intensity number they specify. The actual algorithm used isn’t -important in this example; what’s important is that this calculation takes a -few seconds. We want to call this algorithm only when we need to and only call -it once so we don’t make the user wait more than necessary. - -We’ll simulate calling this hypothetical algorithm with the function -`simulated_expensive_calculation` shown in Listing 13-1, which will print -`calculating slowly...`, wait for two seconds, and then return whatever number -we passed in. +There are many ways to implement this. For this example, we're going to use an +enum called `ShirtColor` that has the variants `Red` and `Blue`. The +company's inventory is represented by an `Inventory` struct that has a field +named `shirts` that contains a `Vec` representing the shirts +currently in stock. The method `shirt_giveaway` defined on `Inventory` gets the +optional shirt color preference of the person getting the free shirt, and +returns the shirt color the person will get. This setup is shown in Listing +13-x: Filename: src/main.rs -```rust -{{#rustdoc_include ../listings/ch13-functional-features/listing-13-01/src/main.rs:here}} +```rust,noplayground +{{#rustdoc_include ../listings/ch13-functional-features/listing-13-01/src/main.rs}} ``` -Listing 13-1: A function to stand in for a hypothetical -calculation that takes about 2 seconds to run +Listing 13-x: Framework of the shirt company giveaway +situation -Next is the `main` function, which contains the parts of the workout app -important for this example. This function represents the code that the app will -call when a user asks for a workout plan. Because the interaction with the -app’s frontend isn’t relevant to the use of closures, we’ll hardcode values -representing inputs to our program and print the outputs. +The `store` defined in `main` has two blue shirts and one red shirt in stock. +Then it calls the `giveaway` method for a user with a preference for a red +shirt and a user without any preference. Running this code prints: -The required inputs are these: - -* An intensity number from the user, which is specified when they request - a workout to indicate whether they want a low-intensity workout or a - high-intensity workout -* A random number that will generate some variety in the workout plans - -The output will be the recommended workout plan. Listing 13-2 shows the `main` -function we’ll use. - -Filename: src/main.rs - -```rust -{{#rustdoc_include ../listings/ch13-functional-features/listing-13-02/src/main.rs:here}} +```console +{{#include ../listings/ch13-functional-features/listing-13-01/output.txt}} ``` -Listing 13-2: A `main` function with hardcoded values to -simulate user input and random number generation +Again, this code could be implemented in many ways, but this way uses concepts +you've already learned, except for the body of the `giveaway` method that uses +a closure. The `giveaway` method takes the user preference `Option` +and calls `unwrap_or_else` on it. The [`unwrap_or_else` method on +`Option`][unwrap-or-else] is defined by the standard library. +It takes one argument: a closure without any arguments that returns a value `T` +(the same type stored in the `Some` variant of the `Option`, in this case, a +`ShirtColor`). If the `Option` is the `Some` variant, `unwrap_or_else` +returns the value from within the `Some`. If the `Option` is the `None` +variant, `unwrap_or_else` calls the closure and returns the value returned by +the closure. -We’ve hardcoded the variable `simulated_user_specified_value` as 10 and the -variable `simulated_random_number` as 7 for simplicity’s sake; in an actual -program, we’d get the intensity number from the app frontend, and we’d use the -`rand` crate to generate a random number, as we did in the Guessing Game -example in Chapter 2. The `main` function calls a `generate_workout` function -with the simulated input values. - -Now that we have the context, let’s get to the algorithm. The function -`generate_workout` in Listing 13-3 contains the business logic of the -app that we’re most concerned with in this example. The rest of the code -changes in this example will be made to this function. - -Filename: src/main.rs - -```rust -{{#rustdoc_include ../listings/ch13-functional-features/listing-13-03/src/main.rs:here}} -``` - -Listing 13-3: The business logic that prints the workout -plans based on the inputs and calls to the `simulated_expensive_calculation` -function - -The code in Listing 13-3 has multiple calls to the slow calculation function. -The first `if` block calls `simulated_expensive_calculation` twice, the `if` -inside the outer `else` doesn’t call it at all, and the code inside the -second `else` case calls it once. - -The desired behavior of the `generate_workout` function is to first check -whether the user wants a low-intensity workout (indicated by a number less than -25) or a high-intensity workout (a number of 25 or greater). - -Low-intensity workout plans will recommend a number of push-ups and sit-ups -based on the complex algorithm we’re simulating. - -If the user wants a high-intensity workout, there’s some additional logic: if -the value of the random number generated by the app happens to be 3, the app -will recommend a break and hydration. If not, the user will get a number of -minutes of running based on the complex algorithm. - -This code works the way the business wants it to now, but let’s say the data -science team decides that we need to make some changes to the way we call the -`simulated_expensive_calculation` function in the future. To simplify the -update when those changes happen, we want to refactor this code so it calls the -`simulated_expensive_calculation` function only once. We also want to cut the -place where we’re currently unnecessarily calling the function twice without -adding any other calls to that function in the process. That is, we don’t want -to call it if the result isn’t needed, and we still want to call it only once. - -#### Refactoring Using Functions - -We could restructure the workout program in many ways. First, we’ll try -extracting the duplicated call to the `simulated_expensive_calculation` -function into a variable, as shown in Listing 13-4. - -Filename: src/main.rs - -```rust -{{#rustdoc_include ../listings/ch13-functional-features/listing-13-04/src/main.rs:here}} -``` - -Listing 13-4: Extracting the calls to -`simulated_expensive_calculation` to one place and storing the result in the -`expensive_result` variable - -This change unifies all the calls to `simulated_expensive_calculation` and -solves the problem of the first `if` block unnecessarily calling the function -twice. Unfortunately, we’re now calling this function and waiting for the -result in all cases, which includes the inner `if` block that doesn’t use the -result value at all. - -We want to refer to `simulated_expensive_calculation` only once in -`generate_workout`, but defer the expensive calculation to only where -we actually need the result. This is a use case for closures! - -#### Refactoring with Closures to Store Code - -Instead of always calling the `simulated_expensive_calculation` function before -the `if` blocks, we can define a closure and store the *closure* in a variable -rather than storing the result of the function call, as shown in Listing 13-5. -We can actually move the whole body of `simulated_expensive_calculation` within -the closure we’re introducing here. - -Filename: src/main.rs - -```rust -{{#rustdoc_include ../listings/ch13-functional-features/listing-13-05/src/main.rs:here}} -``` - -Listing 13-5: Defining a closure and storing it in the -`expensive_closure` variable - -The closure definition comes after the `=` to assign it to the variable -`expensive_closure`. To define a closure, we start with a pair of vertical -pipes (`|`), inside which we specify the parameters to the closure; this syntax -was chosen because of its similarity to closure definitions in Smalltalk and -Ruby. This closure has one parameter named `num`: if we had more than one -parameter, we would separate them with commas, like `|param1, param2|`. - -After the parameters, we place curly brackets that hold the body of the -closure—these are optional if the closure body is a single expression. The end -of the closure, after the curly brackets, needs a semicolon to complete the -`let` statement. The value returned from the last line in the closure body -(`num`) will be the value returned from the closure when it’s called, because -that line doesn’t end in a semicolon; just as in function bodies. - -Note that this `let` statement means `expensive_closure` contains the -*definition* of an anonymous function, not the *resulting value* of calling the -anonymous function. Recall that we’re using a closure because we want to define -the code to call at one point, store that code, and call it at a later point; -the code we want to call is now stored in `expensive_closure`. - -With the closure defined, we can change the code in the `if` blocks to call the -closure to execute the code and get the resulting value. We call a closure like -we do a function: we specify the variable name that holds the closure -definition and follow it with parentheses containing the argument values we -want to use, as shown in Listing 13-6. - -Filename: src/main.rs - -```rust -{{#rustdoc_include ../listings/ch13-functional-features/listing-13-06/src/main.rs:here}} -``` - -Listing 13-6: Calling the `expensive_closure` we’ve -defined - -Now how to perform the expensive calculation is defined in only one -place, and we’re only executing that code where we need the results. - -However, we’ve reintroduced one of the problems from Listing 13-3: we’re still -calling the closure twice in the first `if` block, which will call the -expensive code twice and make the user wait twice as long as they need to. We -could fix this problem by creating a variable local to that `if` block to hold -the result of calling the closure, but closures provide us with another -solution. We’ll talk about that solution in a bit. But first let’s talk about -why there aren’t type annotations in the closure definition and the traits -involved with closures. +This is interesting because we've passed a closure that calls +`self.most_stocked()` on the current `Inventory` instance. The standard library +didn't need to know anything about the `Inventory` or `ShirtColor` types we +defined, or the logic we want to use in this scenario. The closure captured an +immutable reference to the `self` `Inventory` instance and passed it with the +code we specified to the `unwrap_or_else` method. Functions are not able to +capture their environment in this way. ### Closure Type Inference and Annotation -Closures don’t usually require you to annotate the types of the parameters or -the return value like `fn` functions do. Type annotations are required on -functions because they’re part of an explicit interface exposed to your users. -Defining this interface rigidly is important for ensuring that everyone agrees -on what types of values a function uses and returns. But closures aren’t used -in an exposed interface like this: they’re stored in variables and used without -naming them and exposing them to users of our library. +There are more differences between functions and closures. Closures don’t +usually require you to annotate the types of the parameters or the return value +like `fn` functions do. Type annotations are required on functions because +they’re part of an explicit interface exposed to your users. Defining this +interface rigidly is important for ensuring that everyone agrees on what types +of values a function uses and returns. But closures aren’t used in an exposed +interface like this: they’re stored in variables and used without naming them +and exposing them to users of our library. Closures are typically short and relevant only within a narrow context rather than in any arbitrary scenario. Within these limited contexts, the compiler can @@ -217,8 +83,8 @@ needs closure type annotations too). As with variables, we can add type annotations if we want to increase explicitness and clarity at the cost of being more verbose than is strictly -necessary. Annotating the types for the closure we defined in Listing 13-5 -would look like the definition shown in Listing 13-7. +necessary. Annotating the types for a closure would look like the definition +shown in Listing 13-x. Filename: src/main.rs @@ -226,7 +92,7 @@ would look like the definition shown in Listing 13-7. {{#rustdoc_include ../listings/ch13-functional-features/listing-13-07/src/main.rs:here}} ``` -Listing 13-7: Adding optional type annotations of the +Listing 13-x: Adding optional type annotations of the parameter and return value types in the closure With type annotations added, the syntax of closures looks more similar to the @@ -252,7 +118,7 @@ the closures is required for `add_one_v3` and `add_one_v4` to be able to compile because the types will be inferred from their usage. Closure definitions will have one concrete type inferred for each of their -parameters and for their return value. For instance, Listing 13-8 shows the +parameters and for their return value. For instance, Listing 13-x shows the definition of a short closure that just returns the value it receives as a parameter. This closure isn’t very useful except for the purposes of this example. Note that we haven’t added any type annotations to the definition: if @@ -265,7 +131,7 @@ first time and a `u32` the second time, we’ll get an error. {{#rustdoc_include ../listings/ch13-functional-features/listing-13-08/src/main.rs:here}} ``` -Listing 13-8: Attempting to call a closure whose types +Listing 13-x: Attempting to call a closure whose types are inferred with two different types The compiler gives us this error: @@ -279,276 +145,101 @@ infers the type of `x` and the return type of the closure to be `String`. Those types are then locked into the closure in `example_closure`, and we get a type error if we try to use a different type with the same closure. -### Storing Closures Using Generic Parameters and the `Fn` Traits - -Let’s return to our workout generation app. In Listing 13-6, our code was still -calling the expensive calculation closure more times than it needed to. One -option to solve this issue is to save the result of the expensive closure in a -variable for reuse and use the variable in each place we need the result, -instead of calling the closure again. However, this method could result in a -lot of repeated code. - -Fortunately, another solution is available to us. We can create a struct that -will hold the closure and the resulting value of calling the closure. The -struct will execute the closure only if we need the resulting value, and it -will cache the resulting value so the rest of our code doesn’t have to be -responsible for saving and reusing the result. You may know this pattern as -*memoization* or *lazy evaluation*. - -To make a struct that holds a closure, we need to specify the type of the -closure, because a struct definition needs to know the types of each of its -fields. Each closure instance has its own unique anonymous type: that is, even -if two closures have the same signature, their types are still considered -different. To define structs, enums, or function parameters that use closures, -we use generics and trait bounds, as we discussed in Chapter 10. - -The `Fn` traits are provided by the standard library. All closures implement at -least one of the traits: `Fn`, `FnMut`, or `FnOnce`. We’ll discuss the -difference between these traits in the [“Capturing the Environment with -Closures”](#capturing-the-environment-with-closures) section; in -this example, we can use the `Fn` trait. - -We add types to the `Fn` trait bound to represent the types of the parameters -and return values the closures must have to match this trait bound. In this -case, our closure has a parameter of type `u32` and returns a `u32`, so the -trait bound we specify is `Fn(u32) -> u32`. - -Listing 13-9 shows the definition of the `Cacher` struct that holds a closure -and an optional result value. - -Filename: src/main.rs - -```rust -{{#rustdoc_include ../listings/ch13-functional-features/listing-13-09/src/main.rs:here}} -``` - -Listing 13-9: Defining a `Cacher` struct that holds a -closure in `calculation` and an optional result in `value` - -The `Cacher` struct has a `calculation` field of the generic type `T`. The -trait bounds on `T` specify that it’s a closure by using the `Fn` trait. Any -closure we want to store in the `calculation` field must have one `u32` -parameter (specified within the parentheses after `Fn`) and must return a -`u32` (specified after the `->`). - -> Note: Functions can implement all three of the `Fn` traits too. If what we -> want to do doesn’t require capturing a value from the environment, we can use -> a function rather than a closure where we need something that implements an -> `Fn` trait. - -The `value` field is of type `Option`. Before we execute the closure, -`value` will be `None`. When code using a `Cacher` asks for the *result* of the -closure, the `Cacher` will execute the closure at that time and store the -result within a `Some` variant in the `value` field. Then if the code asks for -the result of the closure again, instead of executing the closure again, the -`Cacher` will return the result held in the `Some` variant. - -The logic around the `value` field we’ve just described is defined in Listing -13-10. - -Filename: src/main.rs - -```rust -{{#rustdoc_include ../listings/ch13-functional-features/listing-13-10/src/main.rs:here}} -``` - -Listing 13-10: The caching logic of `Cacher` - -We want `Cacher` to manage the struct fields’ values rather than letting the -calling code potentially change the values in these fields directly, so these -fields are private. - -The `Cacher::new` function takes a generic parameter `T`, which we’ve defined -as having the same trait bound as the `Cacher` struct. Then `Cacher::new` -returns a `Cacher` instance that holds the closure specified in the -`calculation` field and a `None` value in the `value` field, because we haven’t -executed the closure yet. - -When the calling code needs the result of evaluating the closure, instead of -calling the closure directly, it will call the `value` method. This method -checks whether we already have a resulting value in `self.value` in a `Some`; -if we do, it returns the value within the `Some` without executing the closure -again. - -If `self.value` is `None`, the code calls the closure stored in -`self.calculation`, saves the result in `self.value` for future use, and -returns the value as well. - -Listing 13-11 shows how we can use this `Cacher` struct in the function -`generate_workout` from Listing 13-6. - -Filename: src/main.rs - -```rust -{{#rustdoc_include ../listings/ch13-functional-features/listing-13-11/src/main.rs:here}} -``` - -Listing 13-11: Using `Cacher` in the `generate_workout` -function to abstract away the caching logic - -Instead of saving the closure in a variable directly, we save a new instance of -`Cacher` that holds the closure. Then, in each place we want the result, we -call the `value` method on the `Cacher` instance. We can call the `value` -method as many times as we want, or not call it at all, and the expensive -calculation will be run a maximum of once. - -Try running this program with the `main` function from Listing 13-2. Change the -values in the `simulated_user_specified_value` and `simulated_random_number` -variables to verify that in all the cases in the various `if` and `else` -blocks, `calculating slowly...` appears only once and only when needed. The -`Cacher` takes care of the logic necessary to ensure we aren’t calling the -expensive calculation more than we need to so `generate_workout` can focus on -the business logic. - -### Limitations of the `Cacher` Implementation - -Caching values is a generally useful behavior that we might want to use in -other parts of our code with different closures. However, there are two -problems with the current implementation of `Cacher` that would make reusing it -in different contexts difficult. - -The first problem is that a `Cacher` instance assumes it will always get the -same value for the parameter `arg` to the `value` method. That is, this test of -`Cacher` will fail: - -```rust,ignore,panics -{{#rustdoc_include ../listings/ch13-functional-features/no-listing-01-failing-cacher-test/src/lib.rs:here}} -``` - -This test creates a new `Cacher` instance with a closure that returns the value -passed into it. We call the `value` method on this `Cacher` instance with an -`arg` value of 1 and then an `arg` value of 2, and we expect the call to -`value` with the `arg` value of 2 to return 2. - -Run this test with the `Cacher` implementation in Listing 13-9 and Listing -13-10, and the test will fail on the `assert_eq!` with this message: - -```console -{{#include ../listings/ch13-functional-features/no-listing-01-failing-cacher-test/output.txt}} -``` - -The problem is that the first time we called `c.value` with 1, the `Cacher` -instance saved `Some(1)` in `self.value`. Thereafter, no matter what we pass into -the `value` method, it will always return 1. - -Try modifying `Cacher` to hold a hash map rather than a single value. The keys -of the hash map will be the `arg` values that are passed in, and the values of -the hash map will be the result of calling the closure on that key. Instead of -looking at whether `self.value` directly has a `Some` or a `None` value, the -`value` function will look up the `arg` in the hash map and return the value if -it’s present. If it’s not present, the `Cacher` will call the closure and save -the resulting value in the hash map associated with its `arg` value. - -The second problem with the current `Cacher` implementation is that it only -accepts closures that take one parameter of type `u32` and return a `u32`. We -might want to cache the results of closures that take a string slice and return -`usize` values, for example. To fix this issue, try introducing more generic -parameters to increase the flexibility of the `Cacher` functionality. - -### Capturing the Environment with Closures - -In the workout generator example, we only used closures as inline anonymous -functions. However, closures have an additional capability that functions don’t -have: they can capture their environment and access variables from the scope in -which they’re defined. - -Listing 13-12 has an example of a closure stored in the `equal_to_x` variable -that uses the `x` variable from the closure’s surrounding environment. - -Filename: src/main.rs - -```rust -{{#rustdoc_include ../listings/ch13-functional-features/listing-13-12/src/main.rs}} -``` - -Listing 13-12: Example of a closure that refers to a -variable in its enclosing scope - -Here, even though `x` is not one of the parameters of `equal_to_x`, the -`equal_to_x` closure is allowed to use the `x` variable that’s defined in the -same scope that `equal_to_x` is defined in. - -We can’t do the same with functions; if we try with the following example, our -code won’t compile: - -Filename: src/main.rs - -```rust,ignore,does_not_compile -{{#rustdoc_include ../listings/ch13-functional-features/no-listing-02-functions-cant-capture/src/main.rs}} -``` - -We get an error: - -```console -{{#include ../listings/ch13-functional-features/no-listing-02-functions-cant-capture/output.txt}} -``` - -The compiler even reminds us that this only works with closures! - -When a closure captures a value from its environment, it uses memory to store -the values for use in the closure body. This use of memory is overhead that we -don’t want to pay in more common cases where we want to execute code that -doesn’t capture its environment. Because functions are never allowed to capture -their environment, defining and using functions will never incur this overhead. +### Capturing References or Moving Ownership Closures can capture values from their environment in three ways, which -directly map to the three ways a function can take a parameter: taking -ownership, borrowing mutably, and borrowing immutably. These are encoded in the -three `Fn` traits as follows: +directly map to the three ways a function can take a parameter: borrowing +immutably, borrowing mutably, and taking ownership. The closure will decide +which of these to use based on what the body of the function does with the +captured values. -* `FnOnce` consumes the variables it captures from its enclosing scope, known - as the closure’s *environment*. To consume the captured variables, the - closure must take ownership of these variables and move them into the closure - when it is defined. The `Once` part of the name represents the fact that the - closure can’t take ownership of the same variables more than once, so it can - be called only once. -* `FnMut` can change the environment because it mutably borrows values. -* `Fn` borrows values from the environment immutably. - -When you create a closure, Rust infers which trait to use based on how the -closure uses the values from the environment. All closures implement `FnOnce` -because they can all be called at least once. Closures that don’t move the -captured variables also implement `FnMut`, and closures that don’t need mutable -access to the captured variables also implement `Fn`. In Listing 13-12, the -`equal_to_x` closure borrows `x` immutably (so `equal_to_x` has the `Fn` trait) -because the body of the closure only needs to read the value in `x`. - -If you want to force the closure to take ownership of the values it uses in the -environment, you can use the `move` keyword before the parameter list. This -technique is mostly useful when passing a closure to a new thread to move the -data so it’s owned by the new thread. - -> Note: `move` closures may still implement `Fn` or `FnMut`, even though -> they capture variables by move. This is because the traits implemented by a -> closure type are determined by what the closure does with captured values, -> not how it captures them. The `move` keyword only specifies the latter. - -We’ll have more examples of `move` closures in Chapter 16 when we talk about -concurrency. For now, here’s the code from Listing 13-12 with the `move` -keyword added to the closure definition and using vectors instead of integers, -because integers can be copied rather than moved; note that this code will not -yet compile. +Listing 13-x defines a closure that captures an immutable borrow to the vector +named `list` because it only needs an immutable borrow to print the value. This +example also illustrates that a variable can bind to a closure definition, and +the closure can later be called by using the variable name and parentheses as +if the variable name were a function name: Filename: src/main.rs -```rust,ignore,does_not_compile -{{#rustdoc_include ../listings/ch13-functional-features/no-listing-03-move-closures/src/main.rs}} +```rust +fn main() { + let list = vec![1, 2, 3]; + println!("Before defining closure: {:?}", list); + + let only_borrows = || println!("From closure: {:?}", list); + + println!("Before calling closure: {:?}", list); + only_borrows(); + println!("After calling closure: {:?}", list); +} ``` -We receive the following error: +Listing 13-x: Defining and calling a closure that +captures an immutable borrow + +The `list` is still accessible by the code before the closure definition, after +the closure definition but before the closure is called, and after the closure +is called because we can have multiple immutable borrows of `list` at the same +time. This code compiles, runs, and prints: ```console -{{#include ../listings/ch13-functional-features/no-listing-03-move-closures/output.txt}} +Before defining closure: [1, 2, 3] +Before calling closure: [1, 2, 3] +From closure: [1, 2, 3] +After calling closure: [1, 2, 3] ``` -The `x` value is moved into the closure when the closure is defined, because we -added the `move` keyword. The closure then has ownership of `x`, and `main` -isn’t allowed to use `x` anymore in the `println!` statement. Removing -`println!` will fix this example. +Next, Listing 13-x changes the closure definition to need a mutable borrow +because the closure body adds an element to the `list` vector: -Most of the time when specifying one of the `Fn` trait bounds, you can start -with `Fn` and the compiler will tell you if you need `FnMut` or `FnOnce` based -on what happens in the closure body. +Filename: src/main.rs -To illustrate situations where closures that can capture their environment are -useful as function parameters, let’s move on to our next topic: iterators. +```rust +fn main() { + let mut list = vec![1, 2, 3]; + println!("Before defining closure: {:?}", list); + + let mut borrows_mutably = || list.push(7); + + borrows_mutably(); + println!("After calling closure: {:?}", list); +} +``` + +Listing 13-x: Defining and calling a closure that +captures a mutable borrow + +This code compiles, runs, and prints: + +```console +Before defining closure: [1, 2, 3] +After calling closure: [1, 2, 3, 7] +``` + +Note that there's no longer a `println!` between the definition and the call of +the `borrows_mutably` closure: when `borrows_mutably` is defined, it captures a +mutable reference to `list`. After the closure is called, because we don't use +the closure again after that point, the mutable borrow ends. Between the +closure definition and the closure call, an immutable borrow to print isn't +allowed because no other borrows are allowed when there's a mutable borrow. Try +adding a `println!` there to see what error message you get! + + + +If you want to force the closure to take ownership of the values it uses in the +environment even though the body of the closure doesn't strictly need +ownership, you can use the `move` keyword before the parameter list. This +technique is mostly useful when passing a closure to a new thread to move the +data so it’s owned by the new thread. We’ll have more examples of `move` +closures in Chapter 16 when we talk about concurrency. + +### Creating an Abstraction of Behavior with Closures + +#### Refactoring Using Functions + +#### Refactoring with Closures to Store Code + +### Storing Closures Using Generic Parameters and the `Fn` Traits + + +[unwrap-or-else]: ../std/option/enum.Option.html#method.unwrap_or_else diff --git a/src/ch13-02-iterators.md b/src/ch13-02-iterators.md index cc9d16e5f..21a54f724 100644 --- a/src/ch13-02-iterators.md +++ b/src/ch13-02-iterators.md @@ -181,149 +181,3 @@ Because `map` takes a closure, we can specify any operation we want to perform on each item. This is a great example of how closures let you customize some behavior while reusing the iteration behavior that the `Iterator` trait provides. - -### Using Closures that Capture Their Environment - -Now that we’ve introduced iterators, we can demonstrate a common use of -closures that capture their environment by using the `filter` iterator adaptor. -The `filter` method on an iterator takes a closure that takes each item from -the iterator and returns a Boolean. If the closure returns `true`, the value -will be included in the iterator produced by `filter`. If the closure returns -`false`, the value won’t be included in the resulting iterator. - -In Listing 13-19, we use `filter` with a closure that captures the `shoe_size` -variable from its environment to iterate over a collection of `Shoe` struct -instances. It will return only shoes that are the specified size. - -Filename: src/lib.rs - -```rust,noplayground -{{#rustdoc_include ../listings/ch13-functional-features/listing-13-19/src/lib.rs}} -``` - -Listing 13-19: Using the `filter` method with a closure -that captures `shoe_size` - -The `shoes_in_size` function takes ownership of a vector of shoes and a shoe -size as parameters. It returns a vector containing only shoes of the specified -size. - -In the body of `shoes_in_size`, we call `into_iter` to create an iterator -that takes ownership of the vector. Then we call `filter` to adapt that -iterator into a new iterator that only contains elements for which the closure -returns `true`. - -The closure captures the `shoe_size` parameter from the environment and -compares the value with each shoe’s size, keeping only shoes of the size -specified. Finally, calling `collect` gathers the values returned by the -adapted iterator into a vector that’s returned by the function. - -The test shows that when we call `shoes_in_size`, we get back only shoes -that have the same size as the value we specified. - -### Creating Our Own Iterators with the `Iterator` Trait - -We’ve shown that you can create an iterator by calling `iter`, `into_iter`, or -`iter_mut` on a vector. You can create iterators from the other collection -types in the standard library, such as hash map. You can also create iterators -that do anything you want by implementing the `Iterator` trait on your own -types. As previously mentioned, the only method you’re required to provide a -definition for is the `next` method. Once you’ve done that, you can use all -other methods that have default implementations provided by the `Iterator` -trait! - -To demonstrate, let’s create an iterator that will only ever count from 1 to 5. -First, we’ll create a struct to hold some values. Then we’ll make this struct -into an iterator by implementing the `Iterator` trait and using the values in -that implementation. - -Listing 13-20 has the definition of the `Counter` struct and an associated -`new` function to create instances of `Counter`: - -Filename: src/lib.rs - -```rust,noplayground -{{#rustdoc_include ../listings/ch13-functional-features/listing-13-20/src/lib.rs}} -``` - -Listing 13-20: Defining the `Counter` struct and a `new` -function that creates instances of `Counter` with an initial value of 0 for -`count` - -The `Counter` struct has one field named `count`. This field holds a `u32` -value that will keep track of where we are in the process of iterating from 1 -to 5. The `count` field is private because we want the implementation of -`Counter` to manage its value. The `new` function enforces the behavior of -always starting new instances with a value of 0 in the `count` field. - -Next, we’ll implement the `Iterator` trait for our `Counter` type by defining -the body of the `next` method to specify what we want to happen when this -iterator is used, as shown in Listing 13-21: - -Filename: src/lib.rs - -```rust,noplayground -{{#rustdoc_include ../listings/ch13-functional-features/listing-13-21/src/lib.rs:here}} -``` - -Listing 13-21: Implementing the `Iterator` trait on our -`Counter` struct - -We set the associated `Item` type for our iterator to `u32`, meaning the -iterator will return `u32` values. Again, don’t worry about associated types -yet, we’ll cover them in Chapter 19. - -We want our iterator to add 1 to the current state, so we initialized `count` -to 0 so it would return 1 first. If the value of `count` is less than 5, `next` -will increment `count` and return the current value wrapped in `Some`. Once -`count` is 5, our iterator will stop incrementing `count` and always return -`None`. - -#### Using Our `Counter` Iterator’s `next` Method - -Once we’ve implemented the `Iterator` trait, we have an iterator! Listing 13-22 -shows a test demonstrating that we can use the iterator functionality of our -`Counter` struct by calling the `next` method on it directly, just as we did -with the iterator created from a vector in Listing 13-15. - -Filename: src/lib.rs - -```rust,noplayground -{{#rustdoc_include ../listings/ch13-functional-features/listing-13-22/src/lib.rs:here}} -``` - -Listing 13-22: Testing the functionality of the `next` -method implementation - -This test creates a new `Counter` instance in the `counter` variable and then -calls `next` repeatedly, verifying that we have implemented the behavior we -want this iterator to have: returning the values from 1 to 5. - -#### Using Other `Iterator` Trait Methods - -We implemented the `Iterator` trait by defining the `next` method, so we -can now use any `Iterator` trait method’s default implementations as defined in -the standard library, because they all use the `next` method’s functionality. - -For example, if for some reason we wanted to take the values produced by an -instance of `Counter`, pair them with values produced by another `Counter` -instance after skipping the first value, multiply each pair together, keep only -those results that are divisible by 3, and add all the resulting values -together, we could do so, as shown in the test in Listing 13-23: - -Filename: src/lib.rs - -```rust,noplayground -{{#rustdoc_include ../listings/ch13-functional-features/listing-13-23/src/lib.rs:here}} -``` - -Listing 13-23: Using a variety of `Iterator` trait -methods on our `Counter` iterator - -Note that `zip` produces only four pairs; the theoretical fifth pair `(5, -None)` is never produced because `zip` returns `None` when either of its input -iterators return `None`. - -All of these method calls are possible because we specified how the `next` -method works, and the standard library provides default implementations for -other methods that call `next`. From 5e58b9cc3f174d59d17f6039f71d7adc645094ee Mon Sep 17 00:00:00 2001 From: "Carol (Nichols || Goulding)" Date: Fri, 22 Apr 2022 21:03:52 -0400 Subject: [PATCH 04/10] Start of better Fn traits explanation --- ci/dictionary.txt | 1 + src/ch13-01-closures.md | 223 ++++++++++++++++++++++++++++++++++- src/ch13-02-iterators.md | 39 ++++++ src/ch20-02-multithreaded.md | 19 ++- 4 files changed, 266 insertions(+), 16 deletions(-) diff --git a/ci/dictionary.txt b/ci/dictionary.txt index 55740898a..1ecc08811 100644 --- a/ci/dictionary.txt +++ b/ci/dictionary.txt @@ -60,6 +60,7 @@ BoxMeUp BTreeSet BuildHasher Cacher +cacher Cagain callsite CamelCase diff --git a/src/ch13-01-closures.md b/src/ch13-01-closures.md index e951665dc..0ec674f4a 100644 --- a/src/ch13-01-closures.md +++ b/src/ch13-01-closures.md @@ -7,6 +7,11 @@ closures can capture values from the scope in which they’re defined. We’ll demonstrate how these closure features allow for code reuse and behavior customization. + + + + + ### Capturing the Environment with Closures The first aspect of closures we're going to examine is that closures can @@ -224,8 +229,6 @@ closure definition and the closure call, an immutable borrow to print isn't allowed because no other borrows are allowed when there's a mutable borrow. Try adding a `println!` there to see what error message you get! - - If you want to force the closure to take ownership of the values it uses in the environment even though the body of the closure doesn't strictly need ownership, you can use the `move` keyword before the parameter list. This @@ -233,13 +236,221 @@ technique is mostly useful when passing a closure to a new thread to move the data so it’s owned by the new thread. We’ll have more examples of `move` closures in Chapter 16 when we talk about concurrency. -### Creating an Abstraction of Behavior with Closures + + + -#### Refactoring Using Functions +### Moving Captured Values Out of the Closure and the `Fn` Traits -#### Refactoring with Closures to Store Code +Once a closure has captured a reference or moved a value into the closure, the +code in the body of the function also affects what happens to the references or +values as a result of calling the function. A closure body can move a captured +value out of the closure, can mutate the captured value, can neither move nor +mutate the captured value, or can capture nothing from the environment. The way +a closure captures and handles values from the environment affects which traits +the closure implements. The traits are how functions and structs can specify +what kinds of closures they can use. -### Storing Closures Using Generic Parameters and the `Fn` Traits +Closures will automatically implement one, two, or all three of these `Fn` +traits, in an additive fashion: +1. `FnOnce` applies to closures that can be called at least once. All closures + implement this trait, because all closures can be called. If a closure moves + captured values out of its body, then that closure only implements `FnOnce` + and not any of the other `Fn` traits, because it can only be called once. +2. `FnMut` applies to closures that don't move captured values out of their + body, but that might mutate the captured values. These closures can be + called more than once. +3. `Fn` applies to closures that don't move captured values out of their body + and that don't mutate captured values. These closures can be called more + than once without mutating their environment, which is important in cases + such as calling a closure multiple times concurrently. Closures that don't + capture anything from their environment implement `Fn`. + +> Note: Functions can implement all three of the `Fn` traits too. If what we +> want to do doesn’t require capturing a value from the environment, we can use +> a function rather than a closure where we need something that implements one +> of the `Fn` traits. + +Let's look at the definition of the `unwrap_or_else` method on `Option` that +we used in Listing 13-x: + +```rust,ignore +impl Option { + pub fn unwrap_or_else(self, f: F) -> T + where + F: FnOnce() -> T + { + match self { + Some(x) => x, + None => f(), + } + } +} +``` + +Recall that `T` is the generic type representing the type of the value in the +`Some` variant of an `Option`. That type `T` is also the return type of the +`unwrap_or_else` function: code that calls `unwrap_or_else` on an +`Option`, for example, will get a `String`. + +Next, notice that the `unwrap_or_else` function has an additional generic type +parameter, `F`. The `F` type is the type of the parameter named `f`, which is +the closure we provide when calling `unwrap_or_else`. + +The trait bound specified on the generic type `F` is `FnOnce() -> T`, which +means `F` must be able to be called at least once, take no arguments, and +return a `T`. Using `FnOnce` in the trait bound expresses the constraint that +`unwrap_or_else` is only going to call `f` at most one time. In the body of +`unwrap_or_else`, we can see that if the `Option` is `Some`, `f` won't be +called. If the `Option` is `None`, `f` will be called once. Because all +closures implement `FnOnce`, `unwrap_or_else` accepts the most different kinds +of closures and is as flexible as it can be. + +Now let's look at the standard library method, `sort_by_key` defined on slices, +that takes a closure that implements `FnMut` to see how that differs. The +`sort_by_key` method takes a closure that gets one argument, a reference to the +current item in the slice being considered, and returns a value of type `K` +that can be ordered. This function is useful when you want to sort a slice by a +particular attribute of each item. In Listing 13-x, we have a list of +`Rectangle` instances and we use `sort_by_key` to order them by their `width` +attribute from low to high: + +```rust +#[derive(Debug)] +struct Rectangle { + width: u32, + height: u32, +} + +fn main() { + let mut list = [ + Rectangle { width: 10, height: 1 }, + Rectangle { width: 3, height: 5 }, + Rectangle { width: 7, height: 12 }, + ]; + + list.sort_by_key(|r| r.width); + println!("{:#?}", list); +} +``` + +This code prints: + +```console +[ + Rectangle { + width: 3, + height: 5, + }, + Rectangle { + width: 7, + height: 12, + }, + Rectangle { + width: 10, + height: 1, + }, +] +``` + +The reason `sort_by_key` is defined to take an `FnMut` closure is that it calls +the closure multiple times: once for each item in the slice. The closure `|r| +r.width` doesn't capture, mutate, or move out anything from its environment, so +it meets the trait bound requirements. + +In contrast, here's an example of a closure that only implements `FnOnce` +because it moves a value out of the environment. The compiler won't let us use +this closure with `sort_by_key`: + +```rust,ignore,does_not_compile +#[derive(Debug)] +struct Rectangle { + width: u32, + height: u32, +} + +fn main() { + let mut list = [ + Rectangle { width: 10, height: 1 }, + Rectangle { width: 3, height: 5 }, + Rectangle { width: 7, height: 12 }, + ]; + + let mut sort_operations = vec![]; + let value = String::from("by key called"); + + list.sort_by_key(|r| { + sort_operations.push(value); + r.width + }); + println!("{:#?}", list); +} +``` + +This is a contrived, convoluted way (that doesn't work) to try and count the +number of times `sort_by_key` gets called when sorting `list`. This code +attempts to do this counting by pushing `value`, a `String` from the closure's +environment, into the `sort_operations` vector. The closure captures `value` +then moves `value` out of the closure by transferring ownership of `value` to +the `sort_operations` vector. This closure can be called once; trying to call +it a second time wouldn't work because `value` would no longer be in the +environment to be pushed into `sort_operations` again! Therefore, this closure +only implements `FnOnce`. When we try to compile this code, we get this error +that `value` can't be moved out of the closure because the closure must +implement `FnMut`: + +```console +error[E0507]: cannot move out of `value`, a captured variable in an `FnMut` closure + --> src/main.rs:18:30 + | +15 | let value = String::from("by key called"); + | ----- captured outer variable +16 | +17 | list.sort_by_key(|r| { + | ______________________- +18 | | sort_operations.push(value); + | | ^^^^^ move occurs because `value` has type `String`, which does not implement the `Copy` trait +19 | | r.width +20 | | }); + | |_____- captured by this `FnMut` closure +``` + +The error points to the line in the closure body that moves `value` out of the +environment. To fix this, we need to change the closure body so that it doesn't +move values out of the environment. If we're interested in the number of times +`sort_by_key` is called, keeping a counter in the environment and incrementing +its value in the closure body is a more straightforward way to calculate that. +This closure works with `sort_by_key` because it is only capturing a mutable +reference to the `num_sort_operations` counter and can therefore be called more +than once: + +```rust +#[derive(Debug)] +struct Rectangle { + width: u32, + height: u32, +} + +fn main() { + let mut list = [ + Rectangle { width: 10, height: 1 }, + Rectangle { width: 3, height: 5 }, + Rectangle { width: 7, height: 12 }, + ]; + + let mut num_sort_operations = 0; + list.sort_by_key(|r| { + num_sort_operations += 1; + r.width + }); + println!("{:#?}, sorted in {num_sort_operations} operations", list); +} +``` + +The `Fn` traits are important when defining or using functions or types that +make use of closures. The next section discusses iterators, and many iterator +methods take closure arguments. Keep these details of closures in mind as we +explore iterators! [unwrap-or-else]: ../std/option/enum.Option.html#method.unwrap_or_else diff --git a/src/ch13-02-iterators.md b/src/ch13-02-iterators.md index 21a54f724..4c5044693 100644 --- a/src/ch13-02-iterators.md +++ b/src/ch13-02-iterators.md @@ -181,3 +181,42 @@ Because `map` takes a closure, we can specify any operation we want to perform on each item. This is a great example of how closures let you customize some behavior while reusing the iteration behavior that the `Iterator` trait provides. + +### Using Closures that Capture Their Environment + +Now that we’ve introduced iterators, we can demonstrate a common use of +closures that capture their environment by using the `filter` iterator adaptor. +The `filter` method on an iterator takes a closure that takes each item from +the iterator and returns a Boolean. If the closure returns `true`, the value +will be included in the iterator produced by `filter`. If the closure returns +`false`, the value won’t be included in the resulting iterator. + +In Listing 13-19, we use `filter` with a closure that captures the `shoe_size` +variable from its environment to iterate over a collection of `Shoe` struct +instances. It will return only shoes that are the specified size. + +Filename: src/lib.rs + +```rust,noplayground +{{#rustdoc_include ../listings/ch13-functional-features/listing-13-19/src/lib.rs}} +``` + +Listing 13-19: Using the `filter` method with a closure +that captures `shoe_size` + +The `shoes_in_size` function takes ownership of a vector of shoes and a shoe +size as parameters. It returns a vector containing only shoes of the specified +size. + +In the body of `shoes_in_size`, we call `into_iter` to create an iterator +that takes ownership of the vector. Then we call `filter` to adapt that +iterator into a new iterator that only contains elements for which the closure +returns `true`. + +The closure captures the `shoe_size` parameter from the environment and +compares the value with each shoe’s size, keeping only shoes of the size +specified. Finally, calling `collect` gathers the values returned by the +adapted iterator into a vector that’s returned by the function. + +The test shows that when we call `shoes_in_size`, we get back only shoes +that have the same size as the value we specified. diff --git a/src/ch20-02-multithreaded.md b/src/ch20-02-multithreaded.md index 86e7a1259..af52f057e 100644 --- a/src/ch20-02-multithreaded.md +++ b/src/ch20-02-multithreaded.md @@ -214,14 +214,13 @@ so it takes the closure it’s given and gives it to an idle thread in the pool to run. We’ll define the `execute` method on `ThreadPool` to take a closure as a -parameter. Recall from the [“Storing Closures Using Generic Parameters and the -`Fn` Traits”][storing-closures-using-generic-parameters-and-the-fn-traits] section in Chapter 13 that we can take closures as parameters with -three different traits: `Fn`, `FnMut`, and `FnOnce`. We need to decide which -kind of closure to use here. We know we’ll end up doing something similar to -the standard library `thread::spawn` implementation, so we can look at what -bounds the signature of `thread::spawn` has on its parameter. The documentation -shows us the following: +parameter. Recall from the [“Moving Captured Values Out of the Closure and the +`Fn` Traits”][fn-traits] section in Chapter 13 that we can take +closures as parameters with three different traits: `Fn`, `FnMut`, and +`FnOnce`. We need to decide which kind of closure to use here. We know we’ll +end up doing something similar to the standard library `thread::spawn` +implementation, so we can look at what bounds the signature of `thread::spawn` +has on its parameter. The documentation shows us the following: ```rust,ignore pub fn spawn(f: F) -> JoinHandle @@ -679,5 +678,5 @@ of the call to `job()`, meaning other workers cannot receive jobs. [creating-type-synonyms-with-type-aliases]: ch19-04-advanced-types.html#creating-type-synonyms-with-type-aliases [integer-types]: ch03-02-data-types.html#integer-types -[storing-closures-using-generic-parameters-and-the-fn-traits]: -ch13-01-closures.html#storing-closures-using-generic-parameters-and-the-fn-traits +[fn-traits]: +ch13-01-closures.html#moving-captured-values-out-of-the-closure-and-the-fn-traits From 2609c8d610b16175a9f285d40d7cb9220e71d753 Mon Sep 17 00:00:00 2001 From: "Carol (Nichols || Goulding)" Date: Sun, 24 Apr 2022 20:04:42 -0400 Subject: [PATCH 05/10] Add an example of passing a function name to a Fn trait argument --- src/ch13-01-closures.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/ch13-01-closures.md b/src/ch13-01-closures.md index 0ec674f4a..1b7e66c37 100644 --- a/src/ch13-01-closures.md +++ b/src/ch13-01-closures.md @@ -269,8 +269,10 @@ traits, in an additive fashion: > Note: Functions can implement all three of the `Fn` traits too. If what we > want to do doesn’t require capturing a value from the environment, we can use -> a function rather than a closure where we need something that implements one -> of the `Fn` traits. +> the name of a function rather than a closure where we need something that +> implements one of the `Fn` traits. For example, on an `Option>` value, +> we could call `unwrap_or_else(Vec::new)` to get a new, empty vector if the +> value is `None`. Let's look at the definition of the `unwrap_or_else` method on `Option` that we used in Listing 13-x: From de7213a962b61a85f294d9a5411c14da8af57af1 Mon Sep 17 00:00:00 2001 From: "Carol (Nichols || Goulding)" Date: Sun, 24 Apr 2022 20:05:19 -0400 Subject: [PATCH 06/10] Move the function name note a bit later --- src/ch13-01-closures.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/ch13-01-closures.md b/src/ch13-01-closures.md index 1b7e66c37..46325f9e2 100644 --- a/src/ch13-01-closures.md +++ b/src/ch13-01-closures.md @@ -267,13 +267,6 @@ traits, in an additive fashion: such as calling a closure multiple times concurrently. Closures that don't capture anything from their environment implement `Fn`. -> Note: Functions can implement all three of the `Fn` traits too. If what we -> want to do doesn’t require capturing a value from the environment, we can use -> the name of a function rather than a closure where we need something that -> implements one of the `Fn` traits. For example, on an `Option>` value, -> we could call `unwrap_or_else(Vec::new)` to get a new, empty vector if the -> value is `None`. - Let's look at the definition of the `unwrap_or_else` method on `Option` that we used in Listing 13-x: @@ -309,6 +302,13 @@ called. If the `Option` is `None`, `f` will be called once. Because all closures implement `FnOnce`, `unwrap_or_else` accepts the most different kinds of closures and is as flexible as it can be. +> Note: Functions can implement all three of the `Fn` traits too. If what we +> want to do doesn’t require capturing a value from the environment, we can use +> the name of a function rather than a closure where we need something that +> implements one of the `Fn` traits. For example, on an `Option>` value, +> we could call `unwrap_or_else(Vec::new)` to get a new, empty vector if the +> value is `None`. + Now let's look at the standard library method, `sort_by_key` defined on slices, that takes a closure that implements `FnMut` to see how that differs. The `sort_by_key` method takes a closure that gets one argument, a reference to the From bb16c7dfcf108d6215346d5c585e51a76fc9562b Mon Sep 17 00:00:00 2001 From: "Carol (Nichols || Goulding)" Date: Sun, 24 Apr 2022 20:07:05 -0400 Subject: [PATCH 07/10] Minor sentence restructuring --- src/ch13-01-closures.md | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/ch13-01-closures.md b/src/ch13-01-closures.md index 46325f9e2..fde3c32aa 100644 --- a/src/ch13-01-closures.md +++ b/src/ch13-01-closures.md @@ -309,14 +309,13 @@ of closures and is as flexible as it can be. > we could call `unwrap_or_else(Vec::new)` to get a new, empty vector if the > value is `None`. -Now let's look at the standard library method, `sort_by_key` defined on slices, -that takes a closure that implements `FnMut` to see how that differs. The -`sort_by_key` method takes a closure that gets one argument, a reference to the -current item in the slice being considered, and returns a value of type `K` -that can be ordered. This function is useful when you want to sort a slice by a -particular attribute of each item. In Listing 13-x, we have a list of -`Rectangle` instances and we use `sort_by_key` to order them by their `width` -attribute from low to high: +Now let's look at the standard library method `sort_by_key` defined on slices, +to see how that differs. It takes a closure that implements `FnMut`. The +closure gets one argument, a reference to the current item in the slice being +considered, and returns a value of type `K` that can be ordered. This function +is useful when you want to sort a slice by a particular attribute of each item. +In Listing 13-x, we have a list of `Rectangle` instances and we use +`sort_by_key` to order them by their `width` attribute from low to high: ```rust #[derive(Debug)] From 16707ccebdd0a204fc63c6336264a648d6dddea8 Mon Sep 17 00:00:00 2001 From: "Carol (Nichols || Goulding)" Date: Sun, 24 Apr 2022 20:09:02 -0400 Subject: [PATCH 08/10] fancy quotes --- src/ch13-01-closures.md | 58 ++++++++++++++++++++--------------------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/src/ch13-01-closures.md b/src/ch13-01-closures.md index fde3c32aa..5f40a2f83 100644 --- a/src/ch13-01-closures.md +++ b/src/ch13-01-closures.md @@ -14,18 +14,18 @@ customization. ### Capturing the Environment with Closures -The first aspect of closures we're going to examine is that closures can -capture values from the environment they're defined in for later use. Here's +The first aspect of closures we’re going to examine is that closures can +capture values from the environment they’re defined in for later use. Here’s the scenario: A t-shirt company gives away a free shirt to someone on their mailing list every so often. People on the mailing list can optionally add their favorite color to their profile. If the person chosen to get the free shirt has their favorite color in their profile, they get that color shirt. If -the person hasn't specified a favorite color, they get the color that the +the person hasn’t specified a favorite color, they get the color that the company currently has the most of. -There are many ways to implement this. For this example, we're going to use an +There are many ways to implement this. For this example, we’re going to use an enum called `ShirtColor` that has the variants `Red` and `Blue`. The -company's inventory is represented by an `Inventory` struct that has a field +company’s inventory is represented by an `Inventory` struct that has a field named `shirts` that contains a `Vec` representing the shirts currently in stock. The method `shirt_giveaway` defined on `Inventory` gets the optional shirt color preference of the person getting the free shirt, and @@ -50,7 +50,7 @@ shirt and a user without any preference. Running this code prints: ``` Again, this code could be implemented in many ways, but this way uses concepts -you've already learned, except for the body of the `giveaway` method that uses +you’ve already learned, except for the body of the `giveaway` method that uses a closure. The `giveaway` method takes the user preference `Option` and calls `unwrap_or_else` on it. The [`unwrap_or_else` method on `Option`][unwrap-or-else] is defined by the standard library. @@ -61,9 +61,9 @@ returns the value from within the `Some`. If the `Option` is the `None` variant, `unwrap_or_else` calls the closure and returns the value returned by the closure. -This is interesting because we've passed a closure that calls +This is interesting because we’ve passed a closure that calls `self.most_stocked()` on the current `Inventory` instance. The standard library -didn't need to know anything about the `Inventory` or `ShirtColor` types we +didn’t need to know anything about the `Inventory` or `ShirtColor` types we defined, or the logic we want to use in this scenario. The closure captured an immutable reference to the `self` `Inventory` instance and passed it with the code we specified to the `unwrap_or_else` method. Functions are not able to @@ -221,16 +221,16 @@ Before defining closure: [1, 2, 3] After calling closure: [1, 2, 3, 7] ``` -Note that there's no longer a `println!` between the definition and the call of +Note that there’s no longer a `println!` between the definition and the call of the `borrows_mutably` closure: when `borrows_mutably` is defined, it captures a -mutable reference to `list`. After the closure is called, because we don't use +mutable reference to `list`. After the closure is called, because we don’t use the closure again after that point, the mutable borrow ends. Between the -closure definition and the closure call, an immutable borrow to print isn't -allowed because no other borrows are allowed when there's a mutable borrow. Try +closure definition and the closure call, an immutable borrow to print isn’t +allowed because no other borrows are allowed when there’s a mutable borrow. Try adding a `println!` there to see what error message you get! If you want to force the closure to take ownership of the values it uses in the -environment even though the body of the closure doesn't strictly need +environment even though the body of the closure doesn’t strictly need ownership, you can use the `move` keyword before the parameter list. This technique is mostly useful when passing a closure to a new thread to move the data so it’s owned by the new thread. We’ll have more examples of `move` @@ -258,16 +258,16 @@ traits, in an additive fashion: implement this trait, because all closures can be called. If a closure moves captured values out of its body, then that closure only implements `FnOnce` and not any of the other `Fn` traits, because it can only be called once. -2. `FnMut` applies to closures that don't move captured values out of their +2. `FnMut` applies to closures that don’t move captured values out of their body, but that might mutate the captured values. These closures can be called more than once. -3. `Fn` applies to closures that don't move captured values out of their body - and that don't mutate captured values. These closures can be called more +3. `Fn` applies to closures that don’t move captured values out of their body + and that don’t mutate captured values. These closures can be called more than once without mutating their environment, which is important in cases - such as calling a closure multiple times concurrently. Closures that don't + such as calling a closure multiple times concurrently. Closures that don’t capture anything from their environment implement `Fn`. -Let's look at the definition of the `unwrap_or_else` method on `Option` that +Let’s look at the definition of the `unwrap_or_else` method on `Option` that we used in Listing 13-x: ```rust,ignore @@ -297,7 +297,7 @@ The trait bound specified on the generic type `F` is `FnOnce() -> T`, which means `F` must be able to be called at least once, take no arguments, and return a `T`. Using `FnOnce` in the trait bound expresses the constraint that `unwrap_or_else` is only going to call `f` at most one time. In the body of -`unwrap_or_else`, we can see that if the `Option` is `Some`, `f` won't be +`unwrap_or_else`, we can see that if the `Option` is `Some`, `f` won’t be called. If the `Option` is `None`, `f` will be called once. Because all closures implement `FnOnce`, `unwrap_or_else` accepts the most different kinds of closures and is as flexible as it can be. @@ -309,7 +309,7 @@ of closures and is as flexible as it can be. > we could call `unwrap_or_else(Vec::new)` to get a new, empty vector if the > value is `None`. -Now let's look at the standard library method `sort_by_key` defined on slices, +Now let’s look at the standard library method `sort_by_key` defined on slices, to see how that differs. It takes a closure that implements `FnMut`. The closure gets one argument, a reference to the current item in the slice being considered, and returns a value of type `K` that can be ordered. This function @@ -357,11 +357,11 @@ This code prints: The reason `sort_by_key` is defined to take an `FnMut` closure is that it calls the closure multiple times: once for each item in the slice. The closure `|r| -r.width` doesn't capture, mutate, or move out anything from its environment, so +r.width` doesn’t capture, mutate, or move out anything from its environment, so it meets the trait bound requirements. -In contrast, here's an example of a closure that only implements `FnOnce` -because it moves a value out of the environment. The compiler won't let us use +In contrast, here’s an example of a closure that only implements `FnOnce` +because it moves a value out of the environment. The compiler won’t let us use this closure with `sort_by_key`: ```rust,ignore,does_not_compile @@ -389,16 +389,16 @@ fn main() { } ``` -This is a contrived, convoluted way (that doesn't work) to try and count the +This is a contrived, convoluted way (that doesn’t work) to try and count the number of times `sort_by_key` gets called when sorting `list`. This code -attempts to do this counting by pushing `value`, a `String` from the closure's +attempts to do this counting by pushing `value`, a `String` from the closure’s environment, into the `sort_operations` vector. The closure captures `value` then moves `value` out of the closure by transferring ownership of `value` to the `sort_operations` vector. This closure can be called once; trying to call -it a second time wouldn't work because `value` would no longer be in the +it a second time wouldn’t work because `value` would no longer be in the environment to be pushed into `sort_operations` again! Therefore, this closure only implements `FnOnce`. When we try to compile this code, we get this error -that `value` can't be moved out of the closure because the closure must +that `value` can’t be moved out of the closure because the closure must implement `FnMut`: ```console @@ -418,8 +418,8 @@ error[E0507]: cannot move out of `value`, a captured variable in an `FnMut` clos ``` The error points to the line in the closure body that moves `value` out of the -environment. To fix this, we need to change the closure body so that it doesn't -move values out of the environment. If we're interested in the number of times +environment. To fix this, we need to change the closure body so that it doesn’t +move values out of the environment. If we’re interested in the number of times `sort_by_key` is called, keeping a counter in the environment and incrementing its value in the closure body is a more straightforward way to calculate that. This closure works with `sort_by_key` because it is only capturing a mutable From f8c4a88c74d5508424d2d5c2ece96d22a531f015 Mon Sep 17 00:00:00 2001 From: "Carol (Nichols || Goulding)" Date: Sun, 24 Apr 2022 20:14:02 -0400 Subject: [PATCH 09/10] Snapshot of chapter 13 for nostarch --- nostarch/chapter13.md | 1191 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1191 insertions(+) create mode 100644 nostarch/chapter13.md diff --git a/nostarch/chapter13.md b/nostarch/chapter13.md new file mode 100644 index 000000000..f73dc14d8 --- /dev/null +++ b/nostarch/chapter13.md @@ -0,0 +1,1191 @@ + + +[TOC] + +# Functional Language Features: Iterators and Closures + +Rust’s design has taken inspiration from many existing languages and +techniques, and one significant influence is *functional programming*. +Programming in a functional style often includes using functions as values by +passing them in arguments, returning them from other functions, assigning them +to variables for later execution, and so forth. + +In this chapter, we won’t debate the issue of what functional programming is or +isn’t but will instead discuss some features of Rust that are similar to +features in many languages often referred to as functional. + +More specifically, we’ll cover: + +* *Closures*, a function-like construct you can store in a variable +* *Iterators*, a way of processing a series of elements +* How to use these two features to improve the I/O project in Chapter 12 +* The performance of these two features (Spoiler alert: they’re faster than you + might think!) + +Other Rust features, such as pattern matching and enums, which we’ve covered in +other chapters, are influenced by the functional style as well. Mastering +closures and iterators is an important part of writing idiomatic, fast Rust +code, so we’ll devote this entire chapter to them. + +## Closures: Anonymous Functions that Can Capture Their Environment + +Rust’s closures are anonymous functions you can save in a variable or pass as +arguments to other functions. You can create the closure in one place and then +call the closure to evaluate it in a different context. Unlike functions, +closures can capture values from the scope in which they’re defined. We’ll +demonstrate how these closure features allow for code reuse and behavior +customization. + +### Capturing the Environment with Closures + +The first aspect of closures we’re going to examine is that closures can +capture values from the environment they’re defined in for later use. Here’s +the scenario: A t-shirt company gives away a free shirt to someone on their +mailing list every so often. People on the mailing list can optionally add +their favorite color to their profile. If the person chosen to get the free +shirt has their favorite color in their profile, they get that color shirt. If +the person hasn’t specified a favorite color, they get the color that the +company currently has the most of. + +There are many ways to implement this. For this example, we’re going to use an +enum called `ShirtColor` that has the variants `Red` and `Blue`. The +company’s inventory is represented by an `Inventory` struct that has a field +named `shirts` that contains a `Vec` representing the shirts +currently in stock. The method `shirt_giveaway` defined on `Inventory` gets the +optional shirt color preference of the person getting the free shirt, and +returns the shirt color the person will get. This setup is shown in Listing +13-x: + +Filename: src/main.rs + +``` +#[derive(Debug, PartialEq, Copy, Clone)] +enum ShirtColor { + Red, + Blue, +} + +struct Inventory { + shirts: Vec, +} + +impl Inventory { + fn giveaway(&self, user_preference: Option) -> ShirtColor { + user_preference.unwrap_or_else(|| self.most_stocked()) + } + + fn most_stocked(&self) -> ShirtColor { + let mut num_red = 0; + let mut num_blue = 0; + + for color in &self.shirts { + match color { + ShirtColor::Red => num_red += 1, + ShirtColor::Blue => num_blue += 1, + } + } + if num_red > num_blue { + ShirtColor::Red + } else { + ShirtColor::Blue + } + } +} + +fn main() { + let store = Inventory { + shirts: vec![ShirtColor::Blue, ShirtColor::Red, ShirtColor::Blue], + }; + + let user_pref1 = Some(ShirtColor::Red); + let giveaway1 = store.giveaway(user_pref1); + println!( + "The user with preference {:?} gets {:?}", + user_pref1, giveaway1 + ); + + let user_pref2 = None; + let giveaway2 = store.giveaway(user_pref2); + println!( + "The user with preference {:?} gets {:?}", + user_pref2, giveaway2 + ); +} +``` + +Listing 13-x: Framework of the shirt company giveaway situation + +The `store` defined in `main` has two blue shirts and one red shirt in stock. +Then it calls the `giveaway` method for a user with a preference for a red +shirt and a user without any preference. Running this code prints: + +``` +$ cargo run + Compiling shirt-company v0.1.0 (file:///projects/shirt-company) + Finished dev [unoptimized + debuginfo] target(s) in 0.27s + Running `target/debug/shirt-company` +The user with preference Some(Red) gets Red +The user with preference None gets Blue +``` + +Again, this code could be implemented in many ways, but this way uses concepts +you’ve already learned, except for the body of the `giveaway` method that uses +a closure. The `giveaway` method takes the user preference `Option` +and calls `unwrap_or_else` on it. The `unwrap_or_else` method on `Option` is +defined by the standard library. It takes one argument: a closure without any +arguments that returns a value `T` (the same type stored in the `Some` variant +of the `Option`, in this case, a `ShirtColor`). If the `Option` is the +`Some` variant, `unwrap_or_else` returns the value from within the `Some`. If +the `Option` is the `None` variant, `unwrap_or_else` calls the closure and +returns the value returned by the closure. + +This is interesting because we’ve passed a closure that calls +`self.most_stocked()` on the current `Inventory` instance. The standard library +didn’t need to know anything about the `Inventory` or `ShirtColor` types we +defined, or the logic we want to use in this scenario. The closure captured an +immutable reference to the `self` `Inventory` instance and passed it with the +code we specified to the `unwrap_or_else` method. Functions are not able to +capture their environment in this way. + +### Closure Type Inference and Annotation + +There are more differences between functions and closures. Closures don’t +usually require you to annotate the types of the parameters or the return value +like `fn` functions do. Type annotations are required on functions because +they’re part of an explicit interface exposed to your users. Defining this +interface rigidly is important for ensuring that everyone agrees on what types +of values a function uses and returns. But closures aren’t used in an exposed +interface like this: they’re stored in variables and used without naming them +and exposing them to users of our library. + +Closures are typically short and relevant only within a narrow context rather +than in any arbitrary scenario. Within these limited contexts, the compiler can +infer the types of the parameters and the return type, similar to how it’s able +to infer the types of most variables (there are rare cases where the compiler +needs closure type annotations too). + +As with variables, we can add type annotations if we want to increase +explicitness and clarity at the cost of being more verbose than is strictly +necessary. Annotating the types for a closure would look like the definition +shown in Listing 13-x. + +Filename: src/main.rs + +``` +let expensive_closure = |num: u32| -> u32 { + println!("calculating slowly..."); + thread::sleep(Duration::from_secs(2)); + num +}; +``` + +Listing 13-x: Adding optional type annotations of the parameter and return +value types in the closure + +With type annotations added, the syntax of closures looks more similar to the +syntax of functions. The following is a vertical comparison of the syntax for +the definition of a function that adds 1 to its parameter and a closure that +has the same behavior. We’ve added some spaces to line up the relevant parts. +This illustrates how closure syntax is similar to function syntax except for +the use of pipes and the amount of syntax that is optional: + +``` +fn add_one_v1 (x: u32) -> u32 { x + 1 } +let add_one_v2 = |x: u32| -> u32 { x + 1 }; +let add_one_v3 = |x| { x + 1 }; +let add_one_v4 = |x| x + 1 ; +``` + +The first line shows a function definition, and the second line shows a fully +annotated closure definition. The third line removes the type annotations from +the closure definition, and the fourth line removes the brackets, which are +optional because the closure body has only one expression. These are all valid +definitions that will produce the same behavior when they’re called. Calling +the closures is required for `add_one_v3` and `add_one_v4` to be able to +compile because the types will be inferred from their usage. + +Closure definitions will have one concrete type inferred for each of their +parameters and for their return value. For instance, Listing 13-x shows the +definition of a short closure that just returns the value it receives as a +parameter. This closure isn’t very useful except for the purposes of this +example. Note that we haven’t added any type annotations to the definition: if +we then try to call the closure twice, using a `String` as an argument the +first time and a `u32` the second time, we’ll get an error. + +Filename: src/main.rs + +``` +let example_closure = |x| x; + +let s = example_closure(String::from("hello")); +let n = example_closure(5); +``` + +Listing 13-x: Attempting to call a closure whose types are inferred with two different types + +The compiler gives us this error: + +``` +error[E0308]: mismatched types + --> src/main.rs:5:29 + | +5 | let n = example_closure(5); + | ^- help: try using a conversion method: `.to_string()` + | | + | expected struct `String`, found integer +``` + +The first time we call `example_closure` with the `String` value, the compiler +infers the type of `x` and the return type of the closure to be `String`. Those +types are then locked into the closure in `example_closure`, and we get a type +error if we try to use a different type with the same closure. + +### Capturing References or Moving Ownership + +Closures can capture values from their environment in three ways, which +directly map to the three ways a function can take a parameter: borrowing +immutably, borrowing mutably, and taking ownership. The closure will decide +which of these to use based on what the body of the function does with the +captured values. + +Listing 13-x defines a closure that captures an immutable borrow to the vector +named `list` because it only needs an immutable borrow to print the value. This +example also illustrates that a variable can bind to a closure definition, and +the closure can later be called by using the variable name and parentheses as +if the variable name were a function name: + +Filename: src/main.rs + +``` +fn main() { + let list = vec![1, 2, 3]; + println!("Before defining closure: {:?}", list); + + let only_borrows = || println!("From closure: {:?}", list); + + println!("Before calling closure: {:?}", list); + only_borrows(); + println!("After calling closure: {:?}", list); +} +``` + +Listing 13-x: Defining and calling a closure that captures an immutable borrow + +The `list` is still accessible by the code before the closure definition, after +the closure definition but before the closure is called, and after the closure +is called because we can have multiple immutable borrows of `list` at the same +time. This code compiles, runs, and prints: + +``` +Before defining closure: [1, 2, 3] +Before calling closure: [1, 2, 3] +From closure: [1, 2, 3] +After calling closure: [1, 2, 3] +``` + +Next, Listing 13-x changes the closure definition to need a mutable borrow +because the closure body adds an element to the `list` vector: + +Filename: src/main.rs + +``` +fn main() { + let mut list = vec![1, 2, 3]; + println!("Before defining closure: {:?}", list); + + let mut borrows_mutably = || list.push(7); + + borrows_mutably(); + println!("After calling closure: {:?}", list); +} +``` + +Listing 13-x: Defining and calling a closure that captures a mutable borrow + +This code compiles, runs, and prints: + +``` +Before defining closure: [1, 2, 3] +After calling closure: [1, 2, 3, 7] +``` + +Note that there’s no longer a `println!` between the definition and the call of +the `borrows_mutably` closure: when `borrows_mutably` is defined, it captures a +mutable reference to `list`. After the closure is called, because we don’t use +the closure again after that point, the mutable borrow ends. Between the +closure definition and the closure call, an immutable borrow to print isn’t +allowed because no other borrows are allowed when there’s a mutable borrow. Try +adding a `println!` there to see what error message you get! + +If you want to force the closure to take ownership of the values it uses in the +environment even though the body of the closure doesn’t strictly need +ownership, you can use the `move` keyword before the parameter list. This +technique is mostly useful when passing a closure to a new thread to move the +data so it’s owned by the new thread. We’ll have more examples of `move` +closures in Chapter 16 when we talk about concurrency. + +### Moving Captured Values Out of the Closure and the `Fn` Traits + +Once a closure has captured a reference or moved a value into the closure, the +code in the body of the function also affects what happens to the references or +values as a result of calling the function. A closure body can move a captured +value out of the closure, can mutate the captured value, can neither move nor +mutate the captured value, or can capture nothing from the environment. The way +a closure captures and handles values from the environment affects which traits +the closure implements. The traits are how functions and structs can specify +what kinds of closures they can use. + +Closures will automatically implement one, two, or all three of these `Fn` +traits, in an additive fashion: + +1. `FnOnce` applies to closures that can be called at least once. All closures + implement this trait, because all closures can be called. If a closure moves + captured values out of its body, then that closure only implements `FnOnce` + and not any of the other `Fn` traits, because it can only be called once. +2. `FnMut` applies to closures that don’t move captured values out of their + body, but that might mutate the captured values. These closures can be + called more than once. +3. `Fn` applies to closures that don’t move captured values out of their body + and that don’t mutate captured values. These closures can be called more + than once without mutating their environment, which is important in cases + such as calling a closure multiple times concurrently. Closures that don’t + capture anything from their environment implement `Fn`. + +Let’s look at the definition of the `unwrap_or_else` method on `Option` that +we used in Listing 13-x: + +``` +impl Option { + pub fn unwrap_or_else(self, f: F) -> T + where + F: FnOnce() -> T + { + match self { + Some(x) => x, + None => f(), + } + } +} +``` + +Recall that `T` is the generic type representing the type of the value in the +`Some` variant of an `Option`. That type `T` is also the return type of the +`unwrap_or_else` function: code that calls `unwrap_or_else` on an +`Option`, for example, will get a `String`. + +Next, notice that the `unwrap_or_else` function has an additional generic type +parameter, `F`. The `F` type is the type of the parameter named `f`, which is +the closure we provide when calling `unwrap_or_else`. + +The trait bound specified on the generic type `F` is `FnOnce() -> T`, which +means `F` must be able to be called at least once, take no arguments, and +return a `T`. Using `FnOnce` in the trait bound expresses the constraint that +`unwrap_or_else` is only going to call `f` at most one time. In the body of +`unwrap_or_else`, we can see that if the `Option` is `Some`, `f` won’t be +called. If the `Option` is `None`, `f` will be called once. Because all +closures implement `FnOnce`, `unwrap_or_else` accepts the most different kinds +of closures and is as flexible as it can be. + +> Note: Functions can implement all three of the `Fn` traits too. If what we +> want to do doesn’t require capturing a value from the environment, we can use +> the name of a function rather than a closure where we need something that +> implements one of the `Fn` traits. For example, on an `Option>` value, +> we could call `unwrap_or_else(Vec::new)` to get a new, empty vector if the +> value is `None`. + +Now let’s look at the standard library method `sort_by_key` defined on slices, +to see how that differs. It takes a closure that implements `FnMut`. The +closure gets one argument, a reference to the current item in the slice being +considered, and returns a value of type `K` that can be ordered. This function +is useful when you want to sort a slice by a particular attribute of each item. +In Listing 13-x, we have a list of `Rectangle` instances and we use +`sort_by_key` to order them by their `width` attribute from low to high: + +``` +#[derive(Debug)] +struct Rectangle { + width: u32, + height: u32, +} + +fn main() { + let mut list = [ + Rectangle { width: 10, height: 1 }, + Rectangle { width: 3, height: 5 }, + Rectangle { width: 7, height: 12 }, + ]; + + list.sort_by_key(|r| r.width); + println!("{:#?}", list); +} +``` + +This code prints: + +``` +[ + Rectangle { + width: 3, + height: 5, + }, + Rectangle { + width: 7, + height: 12, + }, + Rectangle { + width: 10, + height: 1, + }, +] +``` + +The reason `sort_by_key` is defined to take an `FnMut` closure is that it calls +the closure multiple times: once for each item in the slice. The closure `|r| +r.width` doesn’t capture, mutate, or move out anything from its environment, so +it meets the trait bound requirements. + +In contrast, here’s an example of a closure that only implements `FnOnce` +because it moves a value out of the environment. The compiler won’t let us use +this closure with `sort_by_key`: + +``` +#[derive(Debug)] +struct Rectangle { + width: u32, + height: u32, +} + +fn main() { + let mut list = [ + Rectangle { width: 10, height: 1 }, + Rectangle { width: 3, height: 5 }, + Rectangle { width: 7, height: 12 }, + ]; + + let mut sort_operations = vec![]; + let value = String::from("by key called"); + + list.sort_by_key(|r| { + sort_operations.push(value); + r.width + }); + println!("{:#?}", list); +} +``` + +This is a contrived, convoluted way (that doesn’t work) to try and count the +number of times `sort_by_key` gets called when sorting `list`. This code +attempts to do this counting by pushing `value`, a `String` from the closure’s +environment, into the `sort_operations` vector. The closure captures `value` +then moves `value` out of the closure by transferring ownership of `value` to +the `sort_operations` vector. This closure can be called once; trying to call +it a second time wouldn’t work because `value` would no longer be in the +environment to be pushed into `sort_operations` again! Therefore, this closure +only implements `FnOnce`. When we try to compile this code, we get this error +that `value` can’t be moved out of the closure because the closure must +implement `FnMut`: + +``` +error[E0507]: cannot move out of `value`, a captured variable in an `FnMut` closure + --> src/main.rs:18:30 + | +15 | let value = String::from("by key called"); + | ----- captured outer variable +16 | +17 | list.sort_by_key(|r| { + | ______________________- +18 | | sort_operations.push(value); + | | ^^^^^ move occurs because `value` has type `String`, which does not implement the `Copy` trait +19 | | r.width +20 | | }); + | |_____- captured by this `FnMut` closure +``` + +The error points to the line in the closure body that moves `value` out of the +environment. To fix this, we need to change the closure body so that it doesn’t +move values out of the environment. If we’re interested in the number of times +`sort_by_key` is called, keeping a counter in the environment and incrementing +its value in the closure body is a more straightforward way to calculate that. +This closure works with `sort_by_key` because it is only capturing a mutable +reference to the `num_sort_operations` counter and can therefore be called more +than once: + +``` +#[derive(Debug)] +struct Rectangle { + width: u32, + height: u32, +} + +fn main() { + let mut list = [ + Rectangle { width: 10, height: 1 }, + Rectangle { width: 3, height: 5 }, + Rectangle { width: 7, height: 12 }, + ]; + + let mut num_sort_operations = 0; + list.sort_by_key(|r| { + num_sort_operations += 1; + r.width + }); + println!("{:#?}, sorted in {num_sort_operations} operations", list); +} +``` + +The `Fn` traits are important when defining or using functions or types that +make use of closures. The next section discusses iterators, and many iterator +methods take closure arguments. Keep these details of closures in mind as we +explore iterators! + +## Processing a Series of Items with Iterators + +The iterator pattern allows you to perform some task on a sequence of items in +turn. An iterator is responsible for the logic of iterating over each item and +determining when the sequence has finished. When you use iterators, you don’t +have to reimplement that logic yourself. + +In Rust, iterators are *lazy*, meaning they have no effect until you call +methods that consume the iterator to use it up. For example, the code in +Listing 13-13 creates an iterator over the items in the vector `v1` by calling +the `iter` method defined on `Vec`. This code by itself doesn’t do anything +useful. + +``` +let v1 = vec![1, 2, 3]; + +let v1_iter = v1.iter(); +``` + +Listing 13-13: Creating an iterator + +Once we’ve created an iterator, we can use it in a variety of ways. In Listing +3-5 in Chapter 3, we iterated over an array using a `for` loop to execute some +code on each of its items. Under the hood this implicitly created and then +consumed an iterator, but we glossed over how exactly that works until now. + +The example in Listing 13-14 separates the creation of the iterator from the +use of the iterator in the `for` loop. The iterator is stored in the `v1_iter` +variable, and no iteration takes place at that time. When the `for` loop is +called using the iterator in `v1_iter`, each element in the iterator is used in +one iteration of the loop, which prints out each value. + +``` +let v1 = vec![1, 2, 3]; + +let v1_iter = v1.iter(); + +for val in v1_iter { + println!("Got: {}", val); +} +``` + +Listing 13-14: Using an iterator in a `for` loop + +In languages that don’t have iterators provided by their standard libraries, +you would likely write this same functionality by starting a variable at index +0, using that variable to index into the vector to get a value, and +incrementing the variable value in a loop until it reached the total number of +items in the vector. + +Iterators handle all that logic for you, cutting down on repetitive code you +could potentially mess up. Iterators give you more flexibility to use the same +logic with many different kinds of sequences, not just data structures you can +index into, like vectors. Let’s examine how iterators do that. + +### The `Iterator` Trait and the `next` Method + +All iterators implement a trait named `Iterator` that is defined in the +standard library. The definition of the trait looks like this: + +``` +pub trait Iterator { + type Item; + + fn next(&mut self) -> Option; + + // methods with default implementations elided +} +``` + +Notice this definition uses some new syntax: `type Item` and `Self::Item`, +which are defining an *associated type* with this trait. We’ll talk about +associated types in depth in Chapter 19. For now, all you need to know is that +this code says implementing the `Iterator` trait requires that you also define +an `Item` type, and this `Item` type is used in the return type of the `next` +method. In other words, the `Item` type will be the type returned from the +iterator. + +The `Iterator` trait only requires implementors to define one method: the +`next` method, which returns one item of the iterator at a time wrapped in +`Some` and, when iteration is over, returns `None`. + +We can call the `next` method on iterators directly; Listing 13-15 demonstrates +what values are returned from repeated calls to `next` on the iterator created +from the vector. + +Filename: src/lib.rs + +``` +#[test] +fn iterator_demonstration() { + let v1 = vec![1, 2, 3]; + + let mut v1_iter = v1.iter(); + + assert_eq!(v1_iter.next(), Some(&1)); + assert_eq!(v1_iter.next(), Some(&2)); + assert_eq!(v1_iter.next(), Some(&3)); + assert_eq!(v1_iter.next(), None); +} +``` + +Listing 13-15: Calling the `next` method on an iterator + +Note that we needed to make `v1_iter` mutable: calling the `next` method on an +iterator changes internal state that the iterator uses to keep track of where +it is in the sequence. In other words, this code *consumes*, or uses up, the +iterator. Each call to `next` eats up an item from the iterator. We didn’t need +to make `v1_iter` mutable when we used a `for` loop because the loop took +ownership of `v1_iter` and made it mutable behind the scenes. + +Also note that the values we get from the calls to `next` are immutable +references to the values in the vector. The `iter` method produces an iterator +over immutable references. If we want to create an iterator that takes +ownership of `v1` and returns owned values, we can call `into_iter` instead of +`iter`. Similarly, if we want to iterate over mutable references, we can call +`iter_mut` instead of `iter`. + +### Methods that Consume the Iterator + +The `Iterator` trait has a number of different methods with default +implementations provided by the standard library; you can find out about these +methods by looking in the standard library API documentation for the `Iterator` +trait. Some of these methods call the `next` method in their definition, which +is why you’re required to implement the `next` method when implementing the +`Iterator` trait. + +Methods that call `next` are called *consuming adaptors*, because calling them +uses up the iterator. One example is the `sum` method, which takes ownership of +the iterator and iterates through the items by repeatedly calling `next`, thus +consuming the iterator. As it iterates through, it adds each item to a running +total and returns the total when iteration is complete. Listing 13-16 has a +test illustrating a use of the `sum` method: + +Filename: src/lib.rs + +``` +#[test] +fn iterator_sum() { + let v1 = vec![1, 2, 3]; + + let v1_iter = v1.iter(); + + let total: i32 = v1_iter.sum(); + + assert_eq!(total, 6); +} +``` + +Listing 13-16: Calling the `sum` method to get the total of all items in the iterator + +We aren’t allowed to use `v1_iter` after the call to `sum` because `sum` takes +ownership of the iterator we call it on. + +### Methods that Produce Other Iterators + +Other methods defined on the `Iterator` trait, known as *iterator adaptors*, +allow you to change iterators into different kinds of iterators. You can chain +multiple calls to iterator adaptors to perform complex actions in a readable +way. But because all iterators are lazy, you have to call one of the consuming +adaptor methods to get results from calls to iterator adaptors. + +Listing 13-17 shows an example of calling the iterator adaptor method `map`, +which takes a closure to call on each item to produce a new iterator. The +closure here creates a new iterator in which each item from the vector has been +incremented by 1. However, this code produces a warning: + +Filename: src/main.rs + +``` +let v1: Vec = vec![1, 2, 3]; + +v1.iter().map(|x| x + 1); +``` + +Listing 13-17: Calling the iterator adaptor `map` to +create a new iterator + +The warning we get is this: + +``` +warning: unused `Map` that must be used + --> src/main.rs:4:5 + | +4 | v1.iter().map(|x| x + 1); + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `#[warn(unused_must_use)]` on by default + = note: iterators are lazy and do nothing unless consumed +``` + +The code in Listing 13-17 doesn’t do anything; the closure we’ve specified +never gets called. The warning reminds us why: iterator adaptors are lazy, and +we need to consume the iterator here. + +To fix this and consume the iterator, we’ll use the `collect` method, which we +used in Chapter 12 with `env::args` in Listing 12-1. This method consumes the +iterator and collects the resulting values into a collection data type. + +In Listing 13-18, we collect the results of iterating over the iterator that’s +returned from the call to `map` into a vector. This vector will end up +containing each item from the original vector incremented by 1. + +Filename: src/main.rs + +``` +let v1: Vec = vec![1, 2, 3]; + +let v2: Vec<_> = v1.iter().map(|x| x + 1).collect(); + +assert_eq!(v2, vec![2, 3, 4]); +``` + +Listing 13-18: Calling the `map` method to create a new iterator and then +calling the `collect` method to consume the new iterator and create a vector + +Because `map` takes a closure, we can specify any operation we want to perform +on each item. This is a great example of how closures let you customize some +behavior while reusing the iteration behavior that the `Iterator` trait +provides. + +### Using Closures that Capture Their Environment + +Now that we’ve introduced iterators, we can demonstrate a common use of +closures that capture their environment by using the `filter` iterator adaptor. +The `filter` method on an iterator takes a closure that takes each item from +the iterator and returns a Boolean. If the closure returns `true`, the value +will be included in the iterator produced by `filter`. If the closure returns +`false`, the value won’t be included in the resulting iterator. + +In Listing 13-19, we use `filter` with a closure that captures the `shoe_size` +variable from its environment to iterate over a collection of `Shoe` struct +instances. It will return only shoes that are the specified size. + +Filename: src/lib.rs + +``` +#[derive(PartialEq, Debug)] +struct Shoe { + size: u32, + style: String, +} + +fn shoes_in_size(shoes: Vec, shoe_size: u32) -> Vec { + shoes.into_iter().filter(|s| s.size == shoe_size).collect() +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn filters_by_size() { + let shoes = vec![ + Shoe { + size: 10, + style: String::from("sneaker"), + }, + Shoe { + size: 13, + style: String::from("sandal"), + }, + Shoe { + size: 10, + style: String::from("boot"), + }, + ]; + + let in_my_size = shoes_in_size(shoes, 10); + + assert_eq!( + in_my_size, + vec![ + Shoe { + size: 10, + style: String::from("sneaker") + }, + Shoe { + size: 10, + style: String::from("boot") + }, + ] + ); + } +} +``` + +Listing 13-19: Using the `filter` method with a closure that captures +`shoe_size` + +The `shoes_in_size` function takes ownership of a vector of shoes and a shoe +size as parameters. It returns a vector containing only shoes of the specified +size. + +In the body of `shoes_in_size`, we call `into_iter` to create an iterator +that takes ownership of the vector. Then we call `filter` to adapt that +iterator into a new iterator that only contains elements for which the closure +returns `true`. + +The closure captures the `shoe_size` parameter from the environment and +compares the value with each shoe’s size, keeping only shoes of the size +specified. Finally, calling `collect` gathers the values returned by the +adapted iterator into a vector that’s returned by the function. + +The test shows that when we call `shoes_in_size`, we get back only shoes +that have the same size as the value we specified. + +## Improving Our I/O Project + +With this new knowledge about iterators, we can improve the I/O project in +Chapter 12 by using iterators to make places in the code clearer and more +concise. Let’s look at how iterators can improve our implementation of the +`Config::new` function and the `search` function. + +### Removing a `clone` Using an Iterator + +In Listing 12-6, we added code that took a slice of `String` values and created +an instance of the `Config` struct by indexing into the slice and cloning the +values, allowing the `Config` struct to own those values. In Listing 13-24, +we’ve reproduced the implementation of the `Config::new` function as it was in +Listing 12-23: + +Filename: src/lib.rs + +``` +impl Config { + pub fn new(args: &[String]) -> Result { + if args.len() < 3 { + return Err("not enough arguments"); + } + + let query = args[1].clone(); + let filename = args[2].clone(); + + let case_sensitive = env::var("CASE_INSENSITIVE").is_err(); + + Ok(Config { + query, + filename, + case_sensitive, + }) + } +} +``` + +Listing 13-24: Reproduction of the `Config::new` function from Listing 12-23 + +At the time, we said not to worry about the inefficient `clone` calls because +we would remove them in the future. Well, that time is now! + +We needed `clone` here because we have a slice with `String` elements in the +parameter `args`, but the `new` function doesn’t own `args`. To return +ownership of a `Config` instance, we had to clone the values from the `query` +and `filename` fields of `Config` so the `Config` instance can own its values. + +With our new knowledge about iterators, we can change the `new` function to +take ownership of an iterator as its argument instead of borrowing a slice. +We’ll use the iterator functionality instead of the code that checks the length +of the slice and indexes into specific locations. This will clarify what the +`Config::new` function is doing because the iterator will access the values. + +Once `Config::new` takes ownership of the iterator and stops using indexing +operations that borrow, we can move the `String` values from the iterator into +`Config` rather than calling `clone` and making a new allocation. + +#### Using the Returned Iterator Directly + +Open your I/O project’s *src/main.rs* file, which should look like this: + +Filename: src/main.rs + +``` +fn main() { + let args: Vec = env::args().collect(); + + let config = Config::new(&args).unwrap_or_else(|err| { + eprintln!("Problem parsing arguments: {}", err); + process::exit(1); + }); + + // --snip-- +} +``` + +We’ll change the start of the `main` function that we had in Listing 12-24 to +the code in Listing 13-25. This won’t compile until we update `Config::new` as +well. + +Filename: src/main.rs + +``` +fn main() { + let config = Config::new(env::args()).unwrap_or_else(|err| { + eprintln!("Problem parsing arguments: {}", err); + process::exit(1); + }); + + // --snip-- +} +``` + +Listing 13-25: Passing the return value of `env::args` to `Config::new` + +The `env::args` function returns an iterator! Rather than collecting the +iterator values into a vector and then passing a slice to `Config::new`, now +we’re passing ownership of the iterator returned from `env::args` to +`Config::new` directly. + +Next, we need to update the definition of `Config::new`. In your I/O project’s +*src/lib.rs* file, let’s change the signature of `Config::new` to look like +Listing 13-26. This still won’t compile because we need to update the function +body. + +Filename: src/lib.rs + +``` +impl Config { + pub fn new( + mut args: impl Iterator, + ) -> Result { + // --snip-- +``` + +Listing 13-26: Updating the signature of `Config::new` to expect an iterator + +The standard library documentation for the `env::args` function shows that the +type of the iterator it returns is `std::env::Args`, and that type implements +the `Iterator` trait and returns `String` values. + +We’ve updated the signature of the `Config::new` function so the parameter +`args` has a generic type with the trait bounds `impl Iterator` +instead of `&[String]`. This usage of the `impl Trait` syntax we discussed in +the “Traits as Parameters” section of Chapter 10 +means that `args` can be any type that implements the `Iterator` type and +returns `String` items. + +Because we’re taking ownership of `args` and we’ll be mutating `args` by +iterating over it, we can add the `mut` keyword into the specification of the +`args` parameter to make it mutable. + +#### Using `Iterator` Trait Methods Instead of Indexing + +Next, we’ll fix the body of `Config::new`. Because `args` implements the +`Iterator` trait, we know we can call the `next` method on it! Listing 13-27 +updates the code from Listing 12-23 to use the `next` method: + +Filename: src/lib.rs + +``` +impl Config { + pub fn new( + mut args: impl Iterator, + ) -> Result { + args.next(); + + let query = match args.next() { + Some(arg) => arg, + None => return Err("Didn't get a query string"), + }; + + let filename = match args.next() { + Some(arg) => arg, + None => return Err("Didn't get a file name"), + }; + + let case_sensitive = env::var("CASE_INSENSITIVE").is_err(); + + Ok(Config { + query, + filename, + case_sensitive, + }) + } +} +``` + +Listing 13-27: Changing the body of `Config::new` to use iterator methods + +Remember that the first value in the return value of `env::args` is the name of +the program. We want to ignore that and get to the next value, so first we call +`next` and do nothing with the return value. Second, we call `next` to get the +value we want to put in the `query` field of `Config`. If `next` returns a +`Some`, we use a `match` to extract the value. If it returns `None`, it means +not enough arguments were given and we return early with an `Err` value. We do +the same thing for the `filename` value. + +### Making Code Clearer with Iterator Adaptors + +We can also take advantage of iterators in the `search` function in our I/O +project, which is reproduced here in Listing 13-28 as it was in Listing 12-19: + +Filename: src/lib.rs + +``` +pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> { + let mut results = Vec::new(); + + for line in contents.lines() { + if line.contains(query) { + results.push(line); + } + } + + results +} +``` + +Listing 13-28: The implementation of the `search` function from Listing 12-19 + +We can write this code in a more concise way using iterator adaptor methods. +Doing so also lets us avoid having a mutable intermediate `results` vector. The +functional programming style prefers to minimize the amount of mutable state to +make code clearer. Removing the mutable state might enable a future enhancement +to make searching happen in parallel, because we wouldn’t have to manage +concurrent access to the `results` vector. Listing 13-29 shows this change: + +Filename: src/lib.rs + +``` +pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> { + contents + .lines() + .filter(|line| line.contains(query)) + .collect() +} +``` + +Listing 13-29: Using iterator adaptor methods in the implementation of the +`search` function + +Recall that the purpose of the `search` function is to return all lines in +`contents` that contain the `query`. Similar to the `filter` example in Listing +13-19, this code uses the `filter` adaptor to keep only the lines that +`line.contains(query)` returns `true` for. We then collect the matching lines +into another vector with `collect`. Much simpler! Feel free to make the same +change to use iterator methods in the `search_case_insensitive` function as +well. + +The next logical question is which style you should choose in your own code and +why: the original implementation in Listing 13-28 or the version using +iterators in Listing 13-29. Most Rust programmers prefer to use the iterator +style. It’s a bit tougher to get the hang of at first, but once you get a feel +for the various iterator adaptors and what they do, iterators can be easier to +understand. Instead of fiddling with the various bits of looping and building +new vectors, the code focuses on the high-level objective of the loop. This +abstracts away some of the commonplace code so it’s easier to see the concepts +that are unique to this code, such as the filtering condition each element in +the iterator must pass. + +But are the two implementations truly equivalent? The intuitive assumption +might be that the more low-level loop will be faster. Let’s talk about +performance. + +## Comparing Performance: Loops vs. Iterators + +To determine whether to use loops or iterators, you need to know which +implementation is faster: the version of the `search` function with an explicit +`for` loop or the version with iterators. + +We ran a benchmark by loading the entire contents of *The Adventures of +Sherlock Holmes* by Sir Arthur Conan Doyle into a `String` and looking for the +word *the* in the contents. Here are the results of the benchmark on the +version of `search` using the `for` loop and the version using iterators: + +``` +test bench_search_for ... bench: 19,620,300 ns/iter (+/- 915,700) +test bench_search_iter ... bench: 19,234,900 ns/iter (+/- 657,200) +``` + +The iterator version was slightly faster! We won’t explain the benchmark code +here, because the point is not to prove that the two versions are equivalent +but to get a general sense of how these two implementations compare +performance-wise. + +For a more comprehensive benchmark, you should check using various texts of +various sizes as the `contents`, different words and words of different lengths +as the `query`, and all kinds of other variations. The point is this: +iterators, although a high-level abstraction, get compiled down to roughly the +same code as if you’d written the lower-level code yourself. Iterators are one +of Rust’s *zero-cost abstractions*, by which we mean using the abstraction +imposes no additional runtime overhead. This is analogous to how Bjarne +Stroustrup, the original designer and implementor of C++, defines +*zero-overhead* in “Foundations of C++” (2012): + +> In general, C++ implementations obey the zero-overhead principle: What you +> don’t use, you don’t pay for. And further: What you do use, you couldn’t hand +> code any better. + +As another example, the following code is taken from an audio decoder. The +decoding algorithm uses the linear prediction mathematical operation to +estimate future values based on a linear function of the previous samples. This +code uses an iterator chain to do some math on three variables in scope: a +`buffer` slice of data, an array of 12 `coefficients`, and an amount by which +to shift data in `qlp_shift`. We’ve declared the variables within this example +but not given them any values; although this code doesn’t have much meaning +outside of its context, it’s still a concise, real-world example of how Rust +translates high-level ideas to low-level code. + +``` +let buffer: &mut [i32]; +let coefficients: [i64; 12]; +let qlp_shift: i16; + +for i in 12..buffer.len() { + let prediction = coefficients.iter() + .zip(&buffer[i - 12..i]) + .map(|(&c, &s)| c * s as i64) + .sum::() >> qlp_shift; + let delta = buffer[i]; + buffer[i] = prediction as i32 + delta; +} +``` + +To calculate the value of `prediction`, this code iterates through each of the +12 values in `coefficients` and uses the `zip` method to pair the coefficient +values with the previous 12 values in `buffer`. Then, for each pair, we +multiply the values together, sum all the results, and shift the bits in the +sum `qlp_shift` bits to the right. + +Calculations in applications like audio decoders often prioritize performance +most highly. Here, we’re creating an iterator, using two adaptors, and then +consuming the value. What assembly code would this Rust code compile to? Well, +as of this writing, it compiles down to the same assembly you’d write by hand. +There’s no loop at all corresponding to the iteration over the values in +`coefficients`: Rust knows that there are 12 iterations, so it “unrolls” the +loop. *Unrolling* is an optimization that removes the overhead of the loop +controlling code and instead generates repetitive code for each iteration of +the loop. + +All of the coefficients get stored in registers, which means accessing the +values is very fast. There are no bounds checks on the array access at runtime. +All these optimizations that Rust is able to apply make the resulting code +extremely efficient. Now that you know this, you can use iterators and closures +without fear! They make code seem like it’s higher level but don’t impose a +runtime performance penalty for doing so. + +## Summary + +Closures and iterators are Rust features inspired by functional programming +language ideas. They contribute to Rust’s capability to clearly express +high-level ideas at low-level performance. The implementations of closures and +iterators are such that runtime performance is not affected. This is part of +Rust’s goal to strive to provide zero-cost abstractions. + +Now that we’ve improved the expressiveness of our I/O project, let’s look at +some more features of `cargo` that will help us share the project with the +world. From c25f7183f22bb2d2e5cb4696fec55cf7b78485a9 Mon Sep 17 00:00:00 2001 From: "Carol (Nichols || Goulding)" Date: Tue, 26 Apr 2022 21:31:24 -0400 Subject: [PATCH 10/10] Fixing chapter 13 listings --- .../listing-13-02/src/main.rs | 30 ++- .../listing-13-03/Cargo.lock | 2 +- .../listing-13-03/Cargo.toml | 2 +- .../listing-13-03/output.txt | 12 ++ .../listing-13-03/src/main.rs | 41 +--- .../listing-13-04/Cargo.toml | 2 +- .../listing-13-04/output.txt | 8 + .../listing-13-04/src/main.rs | 36 +--- .../listing-13-05/Cargo.toml | 2 +- .../listing-13-05/output.txt | 6 + .../listing-13-05/src/main.rs | 36 +--- .../listing-13-06/Cargo.lock | 6 - .../listing-13-06/Cargo.toml | 6 - .../listing-13-06/src/main.rs | 33 ---- .../listing-13-07/Cargo.lock | 6 - .../listing-13-07/Cargo.toml | 2 +- .../listing-13-07/output.txt | 18 ++ .../listing-13-07/src/main.rs | 48 ++--- .../listing-13-08/Cargo.lock | 6 - .../listing-13-08/Cargo.toml | 4 +- .../listing-13-08/output.txt | 26 ++- .../listing-13-08/src/main.rs | 37 +++- .../listing-13-09/Cargo.toml | 2 +- .../listing-13-09/src/main.rs | 36 +++- .../listing-13-10/Cargo.lock | 2 +- .../listing-13-10/Cargo.toml | 2 +- .../listing-13-10/src/main.rs | 39 +--- .../listing-13-11/Cargo.lock | 2 +- .../listing-13-11/Cargo.toml | 2 +- .../listing-13-11/src/main.rs | 69 +------ .../listing-13-12/Cargo.lock | 2 +- .../listing-13-12/Cargo.toml | 2 +- .../src/lib.rs | 0 .../listing-13-12/src/main.rs | 9 - .../listing-13-13/src/lib.rs | 15 ++ .../listing-13-13/src/main.rs | 7 - .../output.txt | 0 .../listing-13-14/src/main.rs | 8 +- .../src/main.rs | 4 +- .../listing-13-16/Cargo.lock | 2 +- .../listing-13-16/Cargo.toml | 2 +- .../listing-13-16/src/lib.rs | 49 ++++- .../listing-13-17/Cargo.lock | 6 - .../listing-13-17/Cargo.toml | 6 - .../listing-13-18/Cargo.lock | 2 +- .../listing-13-18/Cargo.toml | 2 +- .../{listing-13-25 => listing-13-18}/poem.txt | 0 .../src/lib.rs | 0 .../listing-13-18/src/main.rs | 24 ++- .../listing-13-19/Cargo.lock | 2 +- .../listing-13-19/Cargo.toml | 2 +- .../{listing-13-26 => listing-13-19}/poem.txt | 0 .../listing-13-19/src/lib.rs | 127 ++++++++---- .../src/main.rs | 0 .../listing-13-20/Cargo.lock | 2 +- .../listing-13-20/Cargo.toml | 2 +- .../{listing-13-27 => listing-13-20}/poem.txt | 0 .../listing-13-20/src/lib.rs | 114 ++++++++++- .../src/main.rs | 0 .../listing-13-21/src/lib.rs | 24 --- .../listing-13-22/Cargo.lock | 2 +- .../listing-13-22/Cargo.toml | 2 +- .../{listing-13-29 => listing-13-22}/poem.txt | 0 .../listing-13-22/src/lib.rs | 113 ++++++++--- .../src/main.rs | 0 .../listing-13-23/Cargo.lock | 6 - .../listing-13-23/Cargo.toml | 6 - .../listing-13-23/src/lib.rs | 51 ----- .../listing-13-25/Cargo.lock | 6 - .../listing-13-25/Cargo.toml | 6 - .../listing-13-25/src/main.rs | 23 --- .../listing-13-26/Cargo.lock | 6 - .../listing-13-26/Cargo.toml | 6 - .../listing-13-26/src/lib.rs | 109 ---------- .../listing-13-27/Cargo.lock | 6 - .../listing-13-27/Cargo.toml | 6 - .../listing-13-27/src/lib.rs | 113 ----------- .../listing-13-29/Cargo.lock | 6 - .../listing-13-29/Cargo.toml | 6 - .../listing-13-29/src/lib.rs | 108 ---------- .../Cargo.lock | 6 - .../Cargo.toml | 6 - .../output.txt | 23 --- .../src/lib.rs | 47 ----- .../Cargo.lock | 6 - .../Cargo.toml | 6 - .../output.txt | 12 -- .../src/main.rs | 11 -- .../no-listing-03-move-closures/Cargo.lock | 6 - .../no-listing-03-move-closures/Cargo.toml | 6 - .../no-listing-03-move-closures/output.txt | 20 -- .../no-listing-03-move-closures/src/main.rs | 11 -- .../listing-13-21-reproduced/Cargo.lock | 6 - .../listing-13-21-reproduced/Cargo.toml | 6 - .../Cargo.lock | 0 .../Cargo.toml | 0 .../src/lib.rs | 0 nostarch/chapter13.md | 187 +++++++++++------- src/ch13-01-closures.md | 178 +++++------------ src/ch13-02-iterators.md | 46 ++--- src/ch13-03-improving-our-io-project.md | 38 ++-- src/ch19-03-advanced-traits.md | 16 +- 102 files changed, 796 insertions(+), 1358 deletions(-) create mode 100644 listings/ch13-functional-features/listing-13-03/output.txt create mode 100644 listings/ch13-functional-features/listing-13-04/output.txt create mode 100644 listings/ch13-functional-features/listing-13-05/output.txt delete mode 100644 listings/ch13-functional-features/listing-13-06/Cargo.lock delete mode 100644 listings/ch13-functional-features/listing-13-06/Cargo.toml delete mode 100644 listings/ch13-functional-features/listing-13-06/src/main.rs delete mode 100644 listings/ch13-functional-features/listing-13-07/Cargo.lock create mode 100644 listings/ch13-functional-features/listing-13-07/output.txt delete mode 100644 listings/ch13-functional-features/listing-13-08/Cargo.lock rename listings/ch13-functional-features/{listing-13-15 => listing-13-12}/src/lib.rs (100%) delete mode 100644 listings/ch13-functional-features/listing-13-12/src/main.rs create mode 100644 listings/ch13-functional-features/listing-13-13/src/lib.rs delete mode 100644 listings/ch13-functional-features/listing-13-13/src/main.rs rename listings/ch13-functional-features/{listing-13-17 => listing-13-14}/output.txt (100%) rename listings/ch13-functional-features/{listing-13-17 => listing-13-15}/src/main.rs (51%) delete mode 100644 listings/ch13-functional-features/listing-13-17/Cargo.lock delete mode 100644 listings/ch13-functional-features/listing-13-17/Cargo.toml rename listings/ch13-functional-features/{listing-13-25 => listing-13-18}/poem.txt (100%) rename listings/ch13-functional-features/{listing-13-25 => listing-13-18}/src/lib.rs (100%) rename listings/ch13-functional-features/{listing-13-26 => listing-13-19}/poem.txt (100%) rename listings/ch13-functional-features/{listing-13-26 => listing-13-19}/src/main.rs (100%) rename listings/ch13-functional-features/{listing-13-27 => listing-13-20}/poem.txt (100%) rename listings/ch13-functional-features/{listing-13-27 => listing-13-20}/src/main.rs (100%) delete mode 100644 listings/ch13-functional-features/listing-13-21/src/lib.rs rename listings/ch13-functional-features/{listing-13-29 => listing-13-22}/poem.txt (100%) rename listings/ch13-functional-features/{listing-13-29 => listing-13-22}/src/main.rs (100%) delete mode 100644 listings/ch13-functional-features/listing-13-23/Cargo.lock delete mode 100644 listings/ch13-functional-features/listing-13-23/Cargo.toml delete mode 100644 listings/ch13-functional-features/listing-13-23/src/lib.rs delete mode 100644 listings/ch13-functional-features/listing-13-25/Cargo.lock delete mode 100644 listings/ch13-functional-features/listing-13-25/Cargo.toml delete mode 100644 listings/ch13-functional-features/listing-13-25/src/main.rs delete mode 100644 listings/ch13-functional-features/listing-13-26/Cargo.lock delete mode 100644 listings/ch13-functional-features/listing-13-26/Cargo.toml delete mode 100644 listings/ch13-functional-features/listing-13-26/src/lib.rs delete mode 100644 listings/ch13-functional-features/listing-13-27/Cargo.lock delete mode 100644 listings/ch13-functional-features/listing-13-27/Cargo.toml delete mode 100644 listings/ch13-functional-features/listing-13-27/src/lib.rs delete mode 100644 listings/ch13-functional-features/listing-13-29/Cargo.lock delete mode 100644 listings/ch13-functional-features/listing-13-29/Cargo.toml delete mode 100644 listings/ch13-functional-features/listing-13-29/src/lib.rs delete mode 100644 listings/ch13-functional-features/no-listing-01-failing-cacher-test/Cargo.lock delete mode 100644 listings/ch13-functional-features/no-listing-01-failing-cacher-test/Cargo.toml delete mode 100644 listings/ch13-functional-features/no-listing-01-failing-cacher-test/output.txt delete mode 100644 listings/ch13-functional-features/no-listing-01-failing-cacher-test/src/lib.rs delete mode 100644 listings/ch13-functional-features/no-listing-02-functions-cant-capture/Cargo.lock delete mode 100644 listings/ch13-functional-features/no-listing-02-functions-cant-capture/Cargo.toml delete mode 100644 listings/ch13-functional-features/no-listing-02-functions-cant-capture/output.txt delete mode 100644 listings/ch13-functional-features/no-listing-02-functions-cant-capture/src/main.rs delete mode 100644 listings/ch13-functional-features/no-listing-03-move-closures/Cargo.lock delete mode 100644 listings/ch13-functional-features/no-listing-03-move-closures/Cargo.toml delete mode 100644 listings/ch13-functional-features/no-listing-03-move-closures/output.txt delete mode 100644 listings/ch13-functional-features/no-listing-03-move-closures/src/main.rs delete mode 100644 listings/ch19-advanced-features/listing-13-21-reproduced/Cargo.lock delete mode 100644 listings/ch19-advanced-features/listing-13-21-reproduced/Cargo.toml rename listings/{ch13-functional-features/listing-13-21 => ch19-advanced-features/no-listing-22-iterator-on-counter}/Cargo.lock (100%) rename listings/{ch13-functional-features/listing-13-21 => ch19-advanced-features/no-listing-22-iterator-on-counter}/Cargo.toml (100%) rename listings/ch19-advanced-features/{listing-13-21-reproduced => no-listing-22-iterator-on-counter}/src/lib.rs (100%) diff --git a/listings/ch13-functional-features/listing-13-02/src/main.rs b/listings/ch13-functional-features/listing-13-02/src/main.rs index 96d06c77d..b3f4cc2c2 100644 --- a/listings/ch13-functional-features/listing-13-02/src/main.rs +++ b/listings/ch13-functional-features/listing-13-02/src/main.rs @@ -1,19 +1,33 @@ use std::thread; use std::time::Duration; -fn simulated_expensive_calculation(intensity: u32) -> u32 { - println!("calculating slowly..."); - thread::sleep(Duration::from_secs(2)); - intensity +fn generate_workout(intensity: u32, random_number: u32) { + // ANCHOR: here + let expensive_closure = |num: u32| -> u32 { + println!("calculating slowly..."); + thread::sleep(Duration::from_secs(2)); + num + }; + // ANCHOR_END: here + + if intensity < 25 { + println!("Today, do {} pushups!", expensive_closure(intensity)); + println!("Next, do {} situps!", expensive_closure(intensity)); + } else { + if random_number == 3 { + println!("Take a break today! Remember to stay hydrated!"); + } else { + println!( + "Today, run for {} minutes!", + expensive_closure(intensity) + ); + } + } } -fn generate_workout(intensity: u32, random_number: u32) {} - -// ANCHOR: here fn main() { let simulated_user_specified_value = 10; let simulated_random_number = 7; generate_workout(simulated_user_specified_value, simulated_random_number); } -// ANCHOR_END: here diff --git a/listings/ch13-functional-features/listing-13-03/Cargo.lock b/listings/ch13-functional-features/listing-13-03/Cargo.lock index 75ff09e51..c190d3a41 100644 --- a/listings/ch13-functional-features/listing-13-03/Cargo.lock +++ b/listings/ch13-functional-features/listing-13-03/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. [[package]] -name = "workout-app" +name = "closure-example" version = "0.1.0" diff --git a/listings/ch13-functional-features/listing-13-03/Cargo.toml b/listings/ch13-functional-features/listing-13-03/Cargo.toml index f09a737d4..914c4cfaa 100644 --- a/listings/ch13-functional-features/listing-13-03/Cargo.toml +++ b/listings/ch13-functional-features/listing-13-03/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "workout-app" +name = "closure-example" version = "0.1.0" edition = "2021" diff --git a/listings/ch13-functional-features/listing-13-03/output.txt b/listings/ch13-functional-features/listing-13-03/output.txt new file mode 100644 index 000000000..37d83618a --- /dev/null +++ b/listings/ch13-functional-features/listing-13-03/output.txt @@ -0,0 +1,12 @@ +$ cargo run + Compiling closure-example v0.1.0 (file:///projects/closure-example) +error[E0308]: mismatched types + --> src/main.rs:5:29 + | +5 | let n = example_closure(5); + | ^- help: try using a conversion method: `.to_string()` + | | + | expected struct `String`, found integer + +For more information about this error, try `rustc --explain E0308`. +error: could not compile `closure-example` due to previous error diff --git a/listings/ch13-functional-features/listing-13-03/src/main.rs b/listings/ch13-functional-features/listing-13-03/src/main.rs index d43c9b211..ebb2489bf 100644 --- a/listings/ch13-functional-features/listing-13-03/src/main.rs +++ b/listings/ch13-functional-features/listing-13-03/src/main.rs @@ -1,39 +1,8 @@ -use std::thread; -use std::time::Duration; - -fn simulated_expensive_calculation(intensity: u32) -> u32 { - println!("calculating slowly..."); - thread::sleep(Duration::from_secs(2)); - intensity -} - -// ANCHOR: here -fn generate_workout(intensity: u32, random_number: u32) { - if intensity < 25 { - println!( - "Today, do {} pushups!", - simulated_expensive_calculation(intensity) - ); - println!( - "Next, do {} situps!", - simulated_expensive_calculation(intensity) - ); - } else { - if random_number == 3 { - println!("Take a break today! Remember to stay hydrated!"); - } else { - println!( - "Today, run for {} minutes!", - simulated_expensive_calculation(intensity) - ); - } - } -} -// ANCHOR_END: here - fn main() { - let simulated_user_specified_value = 10; - let simulated_random_number = 7; + // ANCHOR: here + let example_closure = |x| x; - generate_workout(simulated_user_specified_value, simulated_random_number); + let s = example_closure(String::from("hello")); + let n = example_closure(5); + // ANCHOR_END: here } diff --git a/listings/ch13-functional-features/listing-13-04/Cargo.toml b/listings/ch13-functional-features/listing-13-04/Cargo.toml index f09a737d4..914c4cfaa 100644 --- a/listings/ch13-functional-features/listing-13-04/Cargo.toml +++ b/listings/ch13-functional-features/listing-13-04/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "workout-app" +name = "closure-example" version = "0.1.0" edition = "2021" diff --git a/listings/ch13-functional-features/listing-13-04/output.txt b/listings/ch13-functional-features/listing-13-04/output.txt new file mode 100644 index 000000000..64d763b51 --- /dev/null +++ b/listings/ch13-functional-features/listing-13-04/output.txt @@ -0,0 +1,8 @@ +$ cargo run + Compiling closure-example v0.1.0 (file:///projects/closure-example) + Finished dev [unoptimized + debuginfo] target(s) in 0.43s + Running `target/debug/closure-example` +Before defining closure: [1, 2, 3] +Before calling closure: [1, 2, 3] +From closure: [1, 2, 3] +After calling closure: [1, 2, 3] diff --git a/listings/ch13-functional-features/listing-13-04/src/main.rs b/listings/ch13-functional-features/listing-13-04/src/main.rs index fabe0fb01..43b91bb30 100644 --- a/listings/ch13-functional-features/listing-13-04/src/main.rs +++ b/listings/ch13-functional-features/listing-13-04/src/main.rs @@ -1,32 +1,10 @@ -use std::thread; -use std::time::Duration; - -fn simulated_expensive_calculation(intensity: u32) -> u32 { - println!("calculating slowly..."); - thread::sleep(Duration::from_secs(2)); - intensity -} - -// ANCHOR: here -fn generate_workout(intensity: u32, random_number: u32) { - let expensive_result = simulated_expensive_calculation(intensity); - - if intensity < 25 { - println!("Today, do {} pushups!", expensive_result); - println!("Next, do {} situps!", expensive_result); - } else { - if random_number == 3 { - println!("Take a break today! Remember to stay hydrated!"); - } else { - println!("Today, run for {} minutes!", expensive_result); - } - } -} -// ANCHOR_END: here - fn main() { - let simulated_user_specified_value = 10; - let simulated_random_number = 7; + let list = vec![1, 2, 3]; + println!("Before defining closure: {:?}", list); - generate_workout(simulated_user_specified_value, simulated_random_number); + let only_borrows = || println!("From closure: {:?}", list); + + println!("Before calling closure: {:?}", list); + only_borrows(); + println!("After calling closure: {:?}", list); } diff --git a/listings/ch13-functional-features/listing-13-05/Cargo.toml b/listings/ch13-functional-features/listing-13-05/Cargo.toml index f09a737d4..914c4cfaa 100644 --- a/listings/ch13-functional-features/listing-13-05/Cargo.toml +++ b/listings/ch13-functional-features/listing-13-05/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "workout-app" +name = "closure-example" version = "0.1.0" edition = "2021" diff --git a/listings/ch13-functional-features/listing-13-05/output.txt b/listings/ch13-functional-features/listing-13-05/output.txt new file mode 100644 index 000000000..ce0ad5e37 --- /dev/null +++ b/listings/ch13-functional-features/listing-13-05/output.txt @@ -0,0 +1,6 @@ +$ cargo run + Compiling closure-example v0.1.0 (file:///projects/closure-example) + Finished dev [unoptimized + debuginfo] target(s) in 0.43s + Running `target/debug/closure-example` +Before defining closure: [1, 2, 3] +After calling closure: [1, 2, 3, 7] diff --git a/listings/ch13-functional-features/listing-13-05/src/main.rs b/listings/ch13-functional-features/listing-13-05/src/main.rs index 6984a27a0..37f8130e2 100644 --- a/listings/ch13-functional-features/listing-13-05/src/main.rs +++ b/listings/ch13-functional-features/listing-13-05/src/main.rs @@ -1,33 +1,9 @@ -use std::thread; -use std::time::Duration; - -fn generate_workout(intensity: u32, random_number: u32) { - // ANCHOR: here - let expensive_closure = |num| { - println!("calculating slowly..."); - thread::sleep(Duration::from_secs(2)); - num - }; - // ANCHOR_END: here - - if intensity < 25 { - println!("Today, do {} pushups!", expensive_closure(intensity)); - println!("Next, do {} situps!", expensive_closure(intensity)); - } else { - if random_number == 3 { - println!("Take a break today! Remember to stay hydrated!"); - } else { - println!( - "Today, run for {} minutes!", - expensive_closure(intensity) - ); - } - } -} - fn main() { - let simulated_user_specified_value = 10; - let simulated_random_number = 7; + let mut list = vec![1, 2, 3]; + println!("Before defining closure: {:?}", list); - generate_workout(simulated_user_specified_value, simulated_random_number); + let mut borrows_mutably = || list.push(7); + + borrows_mutably(); + println!("After calling closure: {:?}", list); } diff --git a/listings/ch13-functional-features/listing-13-06/Cargo.lock b/listings/ch13-functional-features/listing-13-06/Cargo.lock deleted file mode 100644 index 75ff09e51..000000000 --- a/listings/ch13-functional-features/listing-13-06/Cargo.lock +++ /dev/null @@ -1,6 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -[[package]] -name = "workout-app" -version = "0.1.0" - diff --git a/listings/ch13-functional-features/listing-13-06/Cargo.toml b/listings/ch13-functional-features/listing-13-06/Cargo.toml deleted file mode 100644 index f09a737d4..000000000 --- a/listings/ch13-functional-features/listing-13-06/Cargo.toml +++ /dev/null @@ -1,6 +0,0 @@ -[package] -name = "workout-app" -version = "0.1.0" -edition = "2021" - -[dependencies] diff --git a/listings/ch13-functional-features/listing-13-06/src/main.rs b/listings/ch13-functional-features/listing-13-06/src/main.rs deleted file mode 100644 index 8850e58a7..000000000 --- a/listings/ch13-functional-features/listing-13-06/src/main.rs +++ /dev/null @@ -1,33 +0,0 @@ -use std::thread; -use std::time::Duration; - -// ANCHOR: here -fn generate_workout(intensity: u32, random_number: u32) { - let expensive_closure = |num| { - println!("calculating slowly..."); - thread::sleep(Duration::from_secs(2)); - num - }; - - if intensity < 25 { - println!("Today, do {} pushups!", expensive_closure(intensity)); - println!("Next, do {} situps!", expensive_closure(intensity)); - } else { - if random_number == 3 { - println!("Take a break today! Remember to stay hydrated!"); - } else { - println!( - "Today, run for {} minutes!", - expensive_closure(intensity) - ); - } - } -} -// ANCHOR_END: here - -fn main() { - let simulated_user_specified_value = 10; - let simulated_random_number = 7; - - generate_workout(simulated_user_specified_value, simulated_random_number); -} diff --git a/listings/ch13-functional-features/listing-13-07/Cargo.lock b/listings/ch13-functional-features/listing-13-07/Cargo.lock deleted file mode 100644 index 75ff09e51..000000000 --- a/listings/ch13-functional-features/listing-13-07/Cargo.lock +++ /dev/null @@ -1,6 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -[[package]] -name = "workout-app" -version = "0.1.0" - diff --git a/listings/ch13-functional-features/listing-13-07/Cargo.toml b/listings/ch13-functional-features/listing-13-07/Cargo.toml index f09a737d4..4a279a450 100644 --- a/listings/ch13-functional-features/listing-13-07/Cargo.toml +++ b/listings/ch13-functional-features/listing-13-07/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "workout-app" +name = "rectangles" version = "0.1.0" edition = "2021" diff --git a/listings/ch13-functional-features/listing-13-07/output.txt b/listings/ch13-functional-features/listing-13-07/output.txt new file mode 100644 index 000000000..f18fce46e --- /dev/null +++ b/listings/ch13-functional-features/listing-13-07/output.txt @@ -0,0 +1,18 @@ +$ cargo run + Compiling rectangles v0.1.0 (file:///projects/rectangles) + Finished dev [unoptimized + debuginfo] target(s) in 0.41s + Running `target/debug/rectangles` +[ + Rectangle { + width: 3, + height: 5, + }, + Rectangle { + width: 7, + height: 12, + }, + Rectangle { + width: 10, + height: 1, + }, +] diff --git a/listings/ch13-functional-features/listing-13-07/src/main.rs b/listings/ch13-functional-features/listing-13-07/src/main.rs index b3f4cc2c2..00f4f515d 100644 --- a/listings/ch13-functional-features/listing-13-07/src/main.rs +++ b/listings/ch13-functional-features/listing-13-07/src/main.rs @@ -1,33 +1,25 @@ -use std::thread; -use std::time::Duration; - -fn generate_workout(intensity: u32, random_number: u32) { - // ANCHOR: here - let expensive_closure = |num: u32| -> u32 { - println!("calculating slowly..."); - thread::sleep(Duration::from_secs(2)); - num - }; - // ANCHOR_END: here - - if intensity < 25 { - println!("Today, do {} pushups!", expensive_closure(intensity)); - println!("Next, do {} situps!", expensive_closure(intensity)); - } else { - if random_number == 3 { - println!("Take a break today! Remember to stay hydrated!"); - } else { - println!( - "Today, run for {} minutes!", - expensive_closure(intensity) - ); - } - } +#[derive(Debug)] +struct Rectangle { + width: u32, + height: u32, } fn main() { - let simulated_user_specified_value = 10; - let simulated_random_number = 7; + let mut list = [ + Rectangle { + width: 10, + height: 1, + }, + Rectangle { + width: 3, + height: 5, + }, + Rectangle { + width: 7, + height: 12, + }, + ]; - generate_workout(simulated_user_specified_value, simulated_random_number); + list.sort_by_key(|r| r.width); + println!("{:#?}", list); } diff --git a/listings/ch13-functional-features/listing-13-08/Cargo.lock b/listings/ch13-functional-features/listing-13-08/Cargo.lock deleted file mode 100644 index c190d3a41..000000000 --- a/listings/ch13-functional-features/listing-13-08/Cargo.lock +++ /dev/null @@ -1,6 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -[[package]] -name = "closure-example" -version = "0.1.0" - diff --git a/listings/ch13-functional-features/listing-13-08/Cargo.toml b/listings/ch13-functional-features/listing-13-08/Cargo.toml index 914c4cfaa..703c9d977 100644 --- a/listings/ch13-functional-features/listing-13-08/Cargo.toml +++ b/listings/ch13-functional-features/listing-13-08/Cargo.toml @@ -1,6 +1,8 @@ [package] -name = "closure-example" +name = "rectangles" version = "0.1.0" edition = "2021" +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + [dependencies] diff --git a/listings/ch13-functional-features/listing-13-08/output.txt b/listings/ch13-functional-features/listing-13-08/output.txt index 37d83618a..3129d5d53 100644 --- a/listings/ch13-functional-features/listing-13-08/output.txt +++ b/listings/ch13-functional-features/listing-13-08/output.txt @@ -1,12 +1,18 @@ $ cargo run - Compiling closure-example v0.1.0 (file:///projects/closure-example) -error[E0308]: mismatched types - --> src/main.rs:5:29 - | -5 | let n = example_closure(5); - | ^- help: try using a conversion method: `.to_string()` - | | - | expected struct `String`, found integer + Compiling rectangles v0.1.0 (file:///projects/rectangles) +error[E0507]: cannot move out of `value`, a captured variable in an `FnMut` closure + --> src/main.rs:27:30 + | +24 | let value = String::from("by key called"); + | ----- captured outer variable +25 | +26 | list.sort_by_key(|r| { + | ______________________- +27 | | sort_operations.push(value); + | | ^^^^^ move occurs because `value` has type `String`, which does not implement the `Copy` trait +28 | | r.width +29 | | }); + | |_____- captured by this `FnMut` closure -For more information about this error, try `rustc --explain E0308`. -error: could not compile `closure-example` due to previous error +For more information about this error, try `rustc --explain E0507`. +error: could not compile `rectangles` due to previous error diff --git a/listings/ch13-functional-features/listing-13-08/src/main.rs b/listings/ch13-functional-features/listing-13-08/src/main.rs index ebb2489bf..2fd15e500 100644 --- a/listings/ch13-functional-features/listing-13-08/src/main.rs +++ b/listings/ch13-functional-features/listing-13-08/src/main.rs @@ -1,8 +1,31 @@ -fn main() { - // ANCHOR: here - let example_closure = |x| x; - - let s = example_closure(String::from("hello")); - let n = example_closure(5); - // ANCHOR_END: here +#[derive(Debug)] +struct Rectangle { + width: u32, + height: u32, +} + +fn main() { + let mut list = [ + Rectangle { + width: 10, + height: 1, + }, + Rectangle { + width: 3, + height: 5, + }, + Rectangle { + width: 7, + height: 12, + }, + ]; + + let mut sort_operations = vec![]; + let value = String::from("by key called"); + + list.sort_by_key(|r| { + sort_operations.push(value); + r.width + }); + println!("{:#?}", list); } diff --git a/listings/ch13-functional-features/listing-13-09/Cargo.toml b/listings/ch13-functional-features/listing-13-09/Cargo.toml index 6b81ef92b..4a279a450 100644 --- a/listings/ch13-functional-features/listing-13-09/Cargo.toml +++ b/listings/ch13-functional-features/listing-13-09/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "cacher" +name = "rectangles" version = "0.1.0" edition = "2021" diff --git a/listings/ch13-functional-features/listing-13-09/src/main.rs b/listings/ch13-functional-features/listing-13-09/src/main.rs index 3fd4ed067..f26252581 100644 --- a/listings/ch13-functional-features/listing-13-09/src/main.rs +++ b/listings/ch13-functional-features/listing-13-09/src/main.rs @@ -1,11 +1,29 @@ -// ANCHOR: here -struct Cacher -where - T: Fn(u32) -> u32, -{ - calculation: T, - value: Option, +#[derive(Debug)] +struct Rectangle { + width: u32, + height: u32, } -// ANCHOR_END: here -fn main() {} +fn main() { + let mut list = [ + Rectangle { + width: 10, + height: 1, + }, + Rectangle { + width: 3, + height: 5, + }, + Rectangle { + width: 7, + height: 12, + }, + ]; + + let mut num_sort_operations = 0; + list.sort_by_key(|r| { + num_sort_operations += 1; + r.width + }); + println!("{:#?}, sorted in {num_sort_operations} operations", list); +} diff --git a/listings/ch13-functional-features/listing-13-10/Cargo.lock b/listings/ch13-functional-features/listing-13-10/Cargo.lock index e090432bc..e91eaa8d4 100644 --- a/listings/ch13-functional-features/listing-13-10/Cargo.lock +++ b/listings/ch13-functional-features/listing-13-10/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. [[package]] -name = "cacher" +name = "iterators" version = "0.1.0" diff --git a/listings/ch13-functional-features/listing-13-10/Cargo.toml b/listings/ch13-functional-features/listing-13-10/Cargo.toml index 6b81ef92b..2652a8a1a 100644 --- a/listings/ch13-functional-features/listing-13-10/Cargo.toml +++ b/listings/ch13-functional-features/listing-13-10/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "cacher" +name = "iterators" version = "0.1.0" edition = "2021" diff --git a/listings/ch13-functional-features/listing-13-10/src/main.rs b/listings/ch13-functional-features/listing-13-10/src/main.rs index 4d1034db9..55a0dd37e 100644 --- a/listings/ch13-functional-features/listing-13-10/src/main.rs +++ b/listings/ch13-functional-features/listing-13-10/src/main.rs @@ -1,34 +1,7 @@ -struct Cacher -where - T: Fn(u32) -> u32, -{ - calculation: T, - value: Option, +fn main() { + // ANCHOR: here + let v1 = vec![1, 2, 3]; + + let v1_iter = v1.iter(); + // ANCHOR_END: here } - -// ANCHOR: here -impl Cacher -where - T: Fn(u32) -> u32, -{ - fn new(calculation: T) -> Cacher { - Cacher { - calculation, - value: None, - } - } - - fn value(&mut self, arg: u32) -> u32 { - match self.value { - Some(v) => v, - None => { - let v = (self.calculation)(arg); - self.value = Some(v); - v - } - } - } -} -// ANCHOR_END: here - -fn main() {} diff --git a/listings/ch13-functional-features/listing-13-11/Cargo.lock b/listings/ch13-functional-features/listing-13-11/Cargo.lock index 75ff09e51..e91eaa8d4 100644 --- a/listings/ch13-functional-features/listing-13-11/Cargo.lock +++ b/listings/ch13-functional-features/listing-13-11/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. [[package]] -name = "workout-app" +name = "iterators" version = "0.1.0" diff --git a/listings/ch13-functional-features/listing-13-11/Cargo.toml b/listings/ch13-functional-features/listing-13-11/Cargo.toml index f09a737d4..2652a8a1a 100644 --- a/listings/ch13-functional-features/listing-13-11/Cargo.toml +++ b/listings/ch13-functional-features/listing-13-11/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "workout-app" +name = "iterators" version = "0.1.0" edition = "2021" diff --git a/listings/ch13-functional-features/listing-13-11/src/main.rs b/listings/ch13-functional-features/listing-13-11/src/main.rs index 9f378b793..712aff408 100644 --- a/listings/ch13-functional-features/listing-13-11/src/main.rs +++ b/listings/ch13-functional-features/listing-13-11/src/main.rs @@ -1,64 +1,11 @@ -use std::thread; -use std::time::Duration; - -struct Cacher -where - T: Fn(u32) -> u32, -{ - calculation: T, - value: Option, -} - -impl Cacher -where - T: Fn(u32) -> u32, -{ - fn new(calculation: T) -> Cacher { - Cacher { - calculation, - value: None, - } - } - - fn value(&mut self, arg: u32) -> u32 { - match self.value { - Some(v) => v, - None => { - let v = (self.calculation)(arg); - self.value = Some(v); - v - } - } - } -} - -// ANCHOR: here -fn generate_workout(intensity: u32, random_number: u32) { - let mut expensive_result = Cacher::new(|num| { - println!("calculating slowly..."); - thread::sleep(Duration::from_secs(2)); - num - }); - - if intensity < 25 { - println!("Today, do {} pushups!", expensive_result.value(intensity)); - println!("Next, do {} situps!", expensive_result.value(intensity)); - } else { - if random_number == 3 { - println!("Take a break today! Remember to stay hydrated!"); - } else { - println!( - "Today, run for {} minutes!", - expensive_result.value(intensity) - ); - } - } -} -// ANCHOR_END: here - fn main() { - let simulated_user_specified_value = 10; - let simulated_random_number = 7; + // ANCHOR: here + let v1 = vec![1, 2, 3]; - generate_workout(simulated_user_specified_value, simulated_random_number); + let v1_iter = v1.iter(); + + for val in v1_iter { + println!("Got: {}", val); + } + // ANCHOR_END: here } diff --git a/listings/ch13-functional-features/listing-13-12/Cargo.lock b/listings/ch13-functional-features/listing-13-12/Cargo.lock index a96532add..e91eaa8d4 100644 --- a/listings/ch13-functional-features/listing-13-12/Cargo.lock +++ b/listings/ch13-functional-features/listing-13-12/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. [[package]] -name = "equal-to-x" +name = "iterators" version = "0.1.0" diff --git a/listings/ch13-functional-features/listing-13-12/Cargo.toml b/listings/ch13-functional-features/listing-13-12/Cargo.toml index ca73ff73d..2652a8a1a 100644 --- a/listings/ch13-functional-features/listing-13-12/Cargo.toml +++ b/listings/ch13-functional-features/listing-13-12/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "equal-to-x" +name = "iterators" version = "0.1.0" edition = "2021" diff --git a/listings/ch13-functional-features/listing-13-15/src/lib.rs b/listings/ch13-functional-features/listing-13-12/src/lib.rs similarity index 100% rename from listings/ch13-functional-features/listing-13-15/src/lib.rs rename to listings/ch13-functional-features/listing-13-12/src/lib.rs diff --git a/listings/ch13-functional-features/listing-13-12/src/main.rs b/listings/ch13-functional-features/listing-13-12/src/main.rs deleted file mode 100644 index 7352b80b2..000000000 --- a/listings/ch13-functional-features/listing-13-12/src/main.rs +++ /dev/null @@ -1,9 +0,0 @@ -fn main() { - let x = 4; - - let equal_to_x = |z| z == x; - - let y = 4; - - assert!(equal_to_x(y)); -} diff --git a/listings/ch13-functional-features/listing-13-13/src/lib.rs b/listings/ch13-functional-features/listing-13-13/src/lib.rs new file mode 100644 index 000000000..d1cb54d0a --- /dev/null +++ b/listings/ch13-functional-features/listing-13-13/src/lib.rs @@ -0,0 +1,15 @@ +#[cfg(test)] +mod tests { + // ANCHOR: here + #[test] + fn iterator_sum() { + let v1 = vec![1, 2, 3]; + + let v1_iter = v1.iter(); + + let total: i32 = v1_iter.sum(); + + assert_eq!(total, 6); + } + // ANCHOR_END: here +} diff --git a/listings/ch13-functional-features/listing-13-13/src/main.rs b/listings/ch13-functional-features/listing-13-13/src/main.rs deleted file mode 100644 index 55a0dd37e..000000000 --- a/listings/ch13-functional-features/listing-13-13/src/main.rs +++ /dev/null @@ -1,7 +0,0 @@ -fn main() { - // ANCHOR: here - let v1 = vec![1, 2, 3]; - - let v1_iter = v1.iter(); - // ANCHOR_END: here -} diff --git a/listings/ch13-functional-features/listing-13-17/output.txt b/listings/ch13-functional-features/listing-13-14/output.txt similarity index 100% rename from listings/ch13-functional-features/listing-13-17/output.txt rename to listings/ch13-functional-features/listing-13-14/output.txt diff --git a/listings/ch13-functional-features/listing-13-14/src/main.rs b/listings/ch13-functional-features/listing-13-14/src/main.rs index 712aff408..62a68be9b 100644 --- a/listings/ch13-functional-features/listing-13-14/src/main.rs +++ b/listings/ch13-functional-features/listing-13-14/src/main.rs @@ -1,11 +1,7 @@ fn main() { // ANCHOR: here - let v1 = vec![1, 2, 3]; + let v1: Vec = vec![1, 2, 3]; - let v1_iter = v1.iter(); - - for val in v1_iter { - println!("Got: {}", val); - } + v1.iter().map(|x| x + 1); // ANCHOR_END: here } diff --git a/listings/ch13-functional-features/listing-13-17/src/main.rs b/listings/ch13-functional-features/listing-13-15/src/main.rs similarity index 51% rename from listings/ch13-functional-features/listing-13-17/src/main.rs rename to listings/ch13-functional-features/listing-13-15/src/main.rs index 62a68be9b..db9025d6f 100644 --- a/listings/ch13-functional-features/listing-13-17/src/main.rs +++ b/listings/ch13-functional-features/listing-13-15/src/main.rs @@ -2,6 +2,8 @@ fn main() { // ANCHOR: here let v1: Vec = vec![1, 2, 3]; - v1.iter().map(|x| x + 1); + let v2: Vec<_> = v1.iter().map(|x| x + 1).collect(); + + assert_eq!(v2, vec![2, 3, 4]); // ANCHOR_END: here } diff --git a/listings/ch13-functional-features/listing-13-16/Cargo.lock b/listings/ch13-functional-features/listing-13-16/Cargo.lock index e91eaa8d4..0b15e2157 100644 --- a/listings/ch13-functional-features/listing-13-16/Cargo.lock +++ b/listings/ch13-functional-features/listing-13-16/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. [[package]] -name = "iterators" +name = "shoe_size" version = "0.1.0" diff --git a/listings/ch13-functional-features/listing-13-16/Cargo.toml b/listings/ch13-functional-features/listing-13-16/Cargo.toml index 2652a8a1a..cc803776b 100644 --- a/listings/ch13-functional-features/listing-13-16/Cargo.toml +++ b/listings/ch13-functional-features/listing-13-16/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "iterators" +name = "shoe_size" version = "0.1.0" edition = "2021" diff --git a/listings/ch13-functional-features/listing-13-16/src/lib.rs b/listings/ch13-functional-features/listing-13-16/src/lib.rs index d1cb54d0a..281c3c9e4 100644 --- a/listings/ch13-functional-features/listing-13-16/src/lib.rs +++ b/listings/ch13-functional-features/listing-13-16/src/lib.rs @@ -1,15 +1,48 @@ +#[derive(PartialEq, Debug)] +struct Shoe { + size: u32, + style: String, +} + +fn shoes_in_size(shoes: Vec, shoe_size: u32) -> Vec { + shoes.into_iter().filter(|s| s.size == shoe_size).collect() +} + #[cfg(test)] mod tests { - // ANCHOR: here + use super::*; + #[test] - fn iterator_sum() { - let v1 = vec![1, 2, 3]; + fn filters_by_size() { + let shoes = vec![ + Shoe { + size: 10, + style: String::from("sneaker"), + }, + Shoe { + size: 13, + style: String::from("sandal"), + }, + Shoe { + size: 10, + style: String::from("boot"), + }, + ]; - let v1_iter = v1.iter(); + let in_my_size = shoes_in_size(shoes, 10); - let total: i32 = v1_iter.sum(); - - assert_eq!(total, 6); + assert_eq!( + in_my_size, + vec![ + Shoe { + size: 10, + style: String::from("sneaker") + }, + Shoe { + size: 10, + style: String::from("boot") + }, + ] + ); } - // ANCHOR_END: here } diff --git a/listings/ch13-functional-features/listing-13-17/Cargo.lock b/listings/ch13-functional-features/listing-13-17/Cargo.lock deleted file mode 100644 index e91eaa8d4..000000000 --- a/listings/ch13-functional-features/listing-13-17/Cargo.lock +++ /dev/null @@ -1,6 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -[[package]] -name = "iterators" -version = "0.1.0" - diff --git a/listings/ch13-functional-features/listing-13-17/Cargo.toml b/listings/ch13-functional-features/listing-13-17/Cargo.toml deleted file mode 100644 index 2652a8a1a..000000000 --- a/listings/ch13-functional-features/listing-13-17/Cargo.toml +++ /dev/null @@ -1,6 +0,0 @@ -[package] -name = "iterators" -version = "0.1.0" -edition = "2021" - -[dependencies] diff --git a/listings/ch13-functional-features/listing-13-18/Cargo.lock b/listings/ch13-functional-features/listing-13-18/Cargo.lock index e91eaa8d4..88bf82d16 100644 --- a/listings/ch13-functional-features/listing-13-18/Cargo.lock +++ b/listings/ch13-functional-features/listing-13-18/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. [[package]] -name = "iterators" +name = "minigrep" version = "0.1.0" diff --git a/listings/ch13-functional-features/listing-13-18/Cargo.toml b/listings/ch13-functional-features/listing-13-18/Cargo.toml index 2652a8a1a..64c2a3f52 100644 --- a/listings/ch13-functional-features/listing-13-18/Cargo.toml +++ b/listings/ch13-functional-features/listing-13-18/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "iterators" +name = "minigrep" version = "0.1.0" edition = "2021" diff --git a/listings/ch13-functional-features/listing-13-25/poem.txt b/listings/ch13-functional-features/listing-13-18/poem.txt similarity index 100% rename from listings/ch13-functional-features/listing-13-25/poem.txt rename to listings/ch13-functional-features/listing-13-18/poem.txt diff --git a/listings/ch13-functional-features/listing-13-25/src/lib.rs b/listings/ch13-functional-features/listing-13-18/src/lib.rs similarity index 100% rename from listings/ch13-functional-features/listing-13-25/src/lib.rs rename to listings/ch13-functional-features/listing-13-18/src/lib.rs diff --git a/listings/ch13-functional-features/listing-13-18/src/main.rs b/listings/ch13-functional-features/listing-13-18/src/main.rs index db9025d6f..d09966e41 100644 --- a/listings/ch13-functional-features/listing-13-18/src/main.rs +++ b/listings/ch13-functional-features/listing-13-18/src/main.rs @@ -1,9 +1,23 @@ +use std::env; +use std::process; + +use minigrep::Config; + +// ANCHOR: here fn main() { - // ANCHOR: here - let v1: Vec = vec![1, 2, 3]; + let config = Config::new(env::args()).unwrap_or_else(|err| { + eprintln!("Problem parsing arguments: {}", err); + process::exit(1); + }); - let v2: Vec<_> = v1.iter().map(|x| x + 1).collect(); - - assert_eq!(v2, vec![2, 3, 4]); + // --snip-- // ANCHOR_END: here + + if let Err(e) = minigrep::run(config) { + eprintln!("Application error: {}", e); + + process::exit(1); + } + // ANCHOR: here } +// ANCHOR_END: here diff --git a/listings/ch13-functional-features/listing-13-19/Cargo.lock b/listings/ch13-functional-features/listing-13-19/Cargo.lock index 0b15e2157..88bf82d16 100644 --- a/listings/ch13-functional-features/listing-13-19/Cargo.lock +++ b/listings/ch13-functional-features/listing-13-19/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. [[package]] -name = "shoe_size" +name = "minigrep" version = "0.1.0" diff --git a/listings/ch13-functional-features/listing-13-19/Cargo.toml b/listings/ch13-functional-features/listing-13-19/Cargo.toml index cc803776b..64c2a3f52 100644 --- a/listings/ch13-functional-features/listing-13-19/Cargo.toml +++ b/listings/ch13-functional-features/listing-13-19/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "shoe_size" +name = "minigrep" version = "0.1.0" edition = "2021" diff --git a/listings/ch13-functional-features/listing-13-26/poem.txt b/listings/ch13-functional-features/listing-13-19/poem.txt similarity index 100% rename from listings/ch13-functional-features/listing-13-26/poem.txt rename to listings/ch13-functional-features/listing-13-19/poem.txt diff --git a/listings/ch13-functional-features/listing-13-19/src/lib.rs b/listings/ch13-functional-features/listing-13-19/src/lib.rs index 281c3c9e4..5127c0a6f 100644 --- a/listings/ch13-functional-features/listing-13-19/src/lib.rs +++ b/listings/ch13-functional-features/listing-13-19/src/lib.rs @@ -1,11 +1,79 @@ -#[derive(PartialEq, Debug)] -struct Shoe { - size: u32, - style: String, +use std::env; +use std::error::Error; +use std::fs; + +pub struct Config { + pub query: String, + pub filename: String, + pub case_sensitive: bool, } -fn shoes_in_size(shoes: Vec, shoe_size: u32) -> Vec { - shoes.into_iter().filter(|s| s.size == shoe_size).collect() +// ANCHOR: here +impl Config { + pub fn new( + mut args: impl Iterator, + ) -> Result { + // --snip-- + // ANCHOR_END: here + if args.len() < 3 { + return Err("not enough arguments"); + } + + let query = args[1].clone(); + let filename = args[2].clone(); + + let case_sensitive = env::var("CASE_INSENSITIVE").is_err(); + + Ok(Config { + query, + filename, + case_sensitive, + }) + } +} + +pub fn run(config: Config) -> Result<(), Box> { + let contents = fs::read_to_string(config.filename)?; + + let results = if config.case_sensitive { + search(&config.query, &contents) + } else { + search_case_insensitive(&config.query, &contents) + }; + + for line in results { + println!("{}", line); + } + + Ok(()) +} + +pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> { + let mut results = Vec::new(); + + for line in contents.lines() { + if line.contains(query) { + results.push(line); + } + } + + results +} + +pub fn search_case_insensitive<'a>( + query: &str, + contents: &'a str, +) -> Vec<&'a str> { + let query = query.to_lowercase(); + let mut results = Vec::new(); + + for line in contents.lines() { + if line.to_lowercase().contains(&query) { + results.push(line); + } + } + + results } #[cfg(test)] @@ -13,36 +81,29 @@ mod tests { use super::*; #[test] - fn filters_by_size() { - let shoes = vec![ - Shoe { - size: 10, - style: String::from("sneaker"), - }, - Shoe { - size: 13, - style: String::from("sandal"), - }, - Shoe { - size: 10, - style: String::from("boot"), - }, - ]; + fn case_sensitive() { + let query = "duct"; + let contents = "\ +Rust: +safe, fast, productive. +Pick three. +Duct tape."; - let in_my_size = shoes_in_size(shoes, 10); + assert_eq!(vec!["safe, fast, productive."], search(query, contents)); + } + + #[test] + fn case_insensitive() { + let query = "rUsT"; + let contents = "\ +Rust: +safe, fast, productive. +Pick three. +Trust me."; assert_eq!( - in_my_size, - vec![ - Shoe { - size: 10, - style: String::from("sneaker") - }, - Shoe { - size: 10, - style: String::from("boot") - }, - ] + vec!["Rust:", "Trust me."], + search_case_insensitive(query, contents) ); } } diff --git a/listings/ch13-functional-features/listing-13-26/src/main.rs b/listings/ch13-functional-features/listing-13-19/src/main.rs similarity index 100% rename from listings/ch13-functional-features/listing-13-26/src/main.rs rename to listings/ch13-functional-features/listing-13-19/src/main.rs diff --git a/listings/ch13-functional-features/listing-13-20/Cargo.lock b/listings/ch13-functional-features/listing-13-20/Cargo.lock index 58b70c5b7..88bf82d16 100644 --- a/listings/ch13-functional-features/listing-13-20/Cargo.lock +++ b/listings/ch13-functional-features/listing-13-20/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. [[package]] -name = "counter" +name = "minigrep" version = "0.1.0" diff --git a/listings/ch13-functional-features/listing-13-20/Cargo.toml b/listings/ch13-functional-features/listing-13-20/Cargo.toml index 9e103f3eb..64c2a3f52 100644 --- a/listings/ch13-functional-features/listing-13-20/Cargo.toml +++ b/listings/ch13-functional-features/listing-13-20/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "counter" +name = "minigrep" version = "0.1.0" edition = "2021" diff --git a/listings/ch13-functional-features/listing-13-27/poem.txt b/listings/ch13-functional-features/listing-13-20/poem.txt similarity index 100% rename from listings/ch13-functional-features/listing-13-27/poem.txt rename to listings/ch13-functional-features/listing-13-20/poem.txt diff --git a/listings/ch13-functional-features/listing-13-20/src/lib.rs b/listings/ch13-functional-features/listing-13-20/src/lib.rs index fb8a0cb2f..8ffad682f 100644 --- a/listings/ch13-functional-features/listing-13-20/src/lib.rs +++ b/listings/ch13-functional-features/listing-13-20/src/lib.rs @@ -1,9 +1,113 @@ -struct Counter { - count: u32, +use std::env; +use std::error::Error; +use std::fs; + +pub struct Config { + pub query: String, + pub filename: String, + pub case_sensitive: bool, } -impl Counter { - fn new() -> Counter { - Counter { count: 0 } +// ANCHOR: here +impl Config { + pub fn new( + mut args: impl Iterator, + ) -> Result { + args.next(); + + let query = match args.next() { + Some(arg) => arg, + None => return Err("Didn't get a query string"), + }; + + let filename = match args.next() { + Some(arg) => arg, + None => return Err("Didn't get a file name"), + }; + + let case_sensitive = env::var("CASE_INSENSITIVE").is_err(); + + Ok(Config { + query, + filename, + case_sensitive, + }) + } +} +// ANCHOR_END: here + +pub fn run(config: Config) -> Result<(), Box> { + let contents = fs::read_to_string(config.filename)?; + + let results = if config.case_sensitive { + search(&config.query, &contents) + } else { + search_case_insensitive(&config.query, &contents) + }; + + for line in results { + println!("{}", line); + } + + Ok(()) +} + +pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> { + let mut results = Vec::new(); + + for line in contents.lines() { + if line.contains(query) { + results.push(line); + } + } + + results +} + +pub fn search_case_insensitive<'a>( + query: &str, + contents: &'a str, +) -> Vec<&'a str> { + let query = query.to_lowercase(); + let mut results = Vec::new(); + + for line in contents.lines() { + if line.to_lowercase().contains(&query) { + results.push(line); + } + } + + results +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn case_sensitive() { + let query = "duct"; + let contents = "\ +Rust: +safe, fast, productive. +Pick three. +Duct tape."; + + assert_eq!(vec!["safe, fast, productive."], search(query, contents)); + } + + #[test] + fn case_insensitive() { + let query = "rUsT"; + let contents = "\ +Rust: +safe, fast, productive. +Pick three. +Trust me."; + + assert_eq!( + vec!["Rust:", "Trust me."], + search_case_insensitive(query, contents) + ); } } diff --git a/listings/ch13-functional-features/listing-13-27/src/main.rs b/listings/ch13-functional-features/listing-13-20/src/main.rs similarity index 100% rename from listings/ch13-functional-features/listing-13-27/src/main.rs rename to listings/ch13-functional-features/listing-13-20/src/main.rs diff --git a/listings/ch13-functional-features/listing-13-21/src/lib.rs b/listings/ch13-functional-features/listing-13-21/src/lib.rs deleted file mode 100644 index 35ea8e5b6..000000000 --- a/listings/ch13-functional-features/listing-13-21/src/lib.rs +++ /dev/null @@ -1,24 +0,0 @@ -struct Counter { - count: u32, -} - -impl Counter { - fn new() -> Counter { - Counter { count: 0 } - } -} - -// ANCHOR: here -impl Iterator for Counter { - type Item = u32; - - fn next(&mut self) -> Option { - if self.count < 5 { - self.count += 1; - Some(self.count) - } else { - None - } - } -} -// ANCHOR_END: here diff --git a/listings/ch13-functional-features/listing-13-22/Cargo.lock b/listings/ch13-functional-features/listing-13-22/Cargo.lock index 58b70c5b7..88bf82d16 100644 --- a/listings/ch13-functional-features/listing-13-22/Cargo.lock +++ b/listings/ch13-functional-features/listing-13-22/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. [[package]] -name = "counter" +name = "minigrep" version = "0.1.0" diff --git a/listings/ch13-functional-features/listing-13-22/Cargo.toml b/listings/ch13-functional-features/listing-13-22/Cargo.toml index 9e103f3eb..64c2a3f52 100644 --- a/listings/ch13-functional-features/listing-13-22/Cargo.toml +++ b/listings/ch13-functional-features/listing-13-22/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "counter" +name = "minigrep" version = "0.1.0" edition = "2021" diff --git a/listings/ch13-functional-features/listing-13-29/poem.txt b/listings/ch13-functional-features/listing-13-22/poem.txt similarity index 100% rename from listings/ch13-functional-features/listing-13-29/poem.txt rename to listings/ch13-functional-features/listing-13-22/poem.txt diff --git a/listings/ch13-functional-features/listing-13-22/src/lib.rs b/listings/ch13-functional-features/listing-13-22/src/lib.rs index 05afa41c8..3ba2cdeec 100644 --- a/listings/ch13-functional-features/listing-13-22/src/lib.rs +++ b/listings/ch13-functional-features/listing-13-22/src/lib.rs @@ -1,41 +1,108 @@ -struct Counter { - count: u32, +use std::env; +use std::error::Error; +use std::fs; + +pub struct Config { + pub query: String, + pub filename: String, + pub case_sensitive: bool, } -impl Counter { - fn new() -> Counter { - Counter { count: 0 } +impl Config { + pub fn new( + mut args: impl Iterator, + ) -> Result { + args.next(); + + let query = match args.next() { + Some(arg) => arg, + None => return Err("Didn't get a query string"), + }; + + let filename = match args.next() { + Some(arg) => arg, + None => return Err("Didn't get a file name"), + }; + + let case_sensitive = env::var("CASE_INSENSITIVE").is_err(); + + Ok(Config { + query, + filename, + case_sensitive, + }) } } -impl Iterator for Counter { - type Item = u32; +pub fn run(config: Config) -> Result<(), Box> { + let contents = fs::read_to_string(config.filename)?; - fn next(&mut self) -> Option { - if self.count < 5 { - self.count += 1; - Some(self.count) - } else { - None + let results = if config.case_sensitive { + search(&config.query, &contents) + } else { + search_case_insensitive(&config.query, &contents) + }; + + for line in results { + println!("{}", line); + } + + Ok(()) +} + +// ANCHOR: here +pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> { + contents + .lines() + .filter(|line| line.contains(query)) + .collect() +} +// ANCHOR_END: here + +pub fn search_case_insensitive<'a>( + query: &str, + contents: &'a str, +) -> Vec<&'a str> { + let query = query.to_lowercase(); + let mut results = Vec::new(); + + for line in contents.lines() { + if line.to_lowercase().contains(&query) { + results.push(line); } } + + results } #[cfg(test)] mod tests { use super::*; - // ANCHOR: here #[test] - fn calling_next_directly() { - let mut counter = Counter::new(); + fn case_sensitive() { + let query = "duct"; + let contents = "\ +Rust: +safe, fast, productive. +Pick three. +Duct tape."; - assert_eq!(counter.next(), Some(1)); - assert_eq!(counter.next(), Some(2)); - assert_eq!(counter.next(), Some(3)); - assert_eq!(counter.next(), Some(4)); - assert_eq!(counter.next(), Some(5)); - assert_eq!(counter.next(), None); + assert_eq!(vec!["safe, fast, productive."], search(query, contents)); + } + + #[test] + fn case_insensitive() { + let query = "rUsT"; + let contents = "\ +Rust: +safe, fast, productive. +Pick three. +Trust me."; + + assert_eq!( + vec!["Rust:", "Trust me."], + search_case_insensitive(query, contents) + ); } - // ANCHOR_END: here } diff --git a/listings/ch13-functional-features/listing-13-29/src/main.rs b/listings/ch13-functional-features/listing-13-22/src/main.rs similarity index 100% rename from listings/ch13-functional-features/listing-13-29/src/main.rs rename to listings/ch13-functional-features/listing-13-22/src/main.rs diff --git a/listings/ch13-functional-features/listing-13-23/Cargo.lock b/listings/ch13-functional-features/listing-13-23/Cargo.lock deleted file mode 100644 index 58b70c5b7..000000000 --- a/listings/ch13-functional-features/listing-13-23/Cargo.lock +++ /dev/null @@ -1,6 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -[[package]] -name = "counter" -version = "0.1.0" - diff --git a/listings/ch13-functional-features/listing-13-23/Cargo.toml b/listings/ch13-functional-features/listing-13-23/Cargo.toml deleted file mode 100644 index 9e103f3eb..000000000 --- a/listings/ch13-functional-features/listing-13-23/Cargo.toml +++ /dev/null @@ -1,6 +0,0 @@ -[package] -name = "counter" -version = "0.1.0" -edition = "2021" - -[dependencies] diff --git a/listings/ch13-functional-features/listing-13-23/src/lib.rs b/listings/ch13-functional-features/listing-13-23/src/lib.rs deleted file mode 100644 index f04d7304c..000000000 --- a/listings/ch13-functional-features/listing-13-23/src/lib.rs +++ /dev/null @@ -1,51 +0,0 @@ -struct Counter { - count: u32, -} - -impl Counter { - fn new() -> Counter { - Counter { count: 0 } - } -} - -impl Iterator for Counter { - type Item = u32; - - fn next(&mut self) -> Option { - if self.count < 5 { - self.count += 1; - Some(self.count) - } else { - None - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn calling_next_directly() { - let mut counter = Counter::new(); - - assert_eq!(counter.next(), Some(1)); - assert_eq!(counter.next(), Some(2)); - assert_eq!(counter.next(), Some(3)); - assert_eq!(counter.next(), Some(4)); - assert_eq!(counter.next(), Some(5)); - assert_eq!(counter.next(), None); - } - - // ANCHOR: here - #[test] - fn using_other_iterator_trait_methods() { - let sum: u32 = Counter::new() - .zip(Counter::new().skip(1)) - .map(|(a, b)| a * b) - .filter(|x| x % 3 == 0) - .sum(); - assert_eq!(18, sum); - } - // ANCHOR_END: here -} diff --git a/listings/ch13-functional-features/listing-13-25/Cargo.lock b/listings/ch13-functional-features/listing-13-25/Cargo.lock deleted file mode 100644 index 88bf82d16..000000000 --- a/listings/ch13-functional-features/listing-13-25/Cargo.lock +++ /dev/null @@ -1,6 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -[[package]] -name = "minigrep" -version = "0.1.0" - diff --git a/listings/ch13-functional-features/listing-13-25/Cargo.toml b/listings/ch13-functional-features/listing-13-25/Cargo.toml deleted file mode 100644 index 64c2a3f52..000000000 --- a/listings/ch13-functional-features/listing-13-25/Cargo.toml +++ /dev/null @@ -1,6 +0,0 @@ -[package] -name = "minigrep" -version = "0.1.0" -edition = "2021" - -[dependencies] diff --git a/listings/ch13-functional-features/listing-13-25/src/main.rs b/listings/ch13-functional-features/listing-13-25/src/main.rs deleted file mode 100644 index d09966e41..000000000 --- a/listings/ch13-functional-features/listing-13-25/src/main.rs +++ /dev/null @@ -1,23 +0,0 @@ -use std::env; -use std::process; - -use minigrep::Config; - -// ANCHOR: here -fn main() { - let config = Config::new(env::args()).unwrap_or_else(|err| { - eprintln!("Problem parsing arguments: {}", err); - process::exit(1); - }); - - // --snip-- - // ANCHOR_END: here - - if let Err(e) = minigrep::run(config) { - eprintln!("Application error: {}", e); - - process::exit(1); - } - // ANCHOR: here -} -// ANCHOR_END: here diff --git a/listings/ch13-functional-features/listing-13-26/Cargo.lock b/listings/ch13-functional-features/listing-13-26/Cargo.lock deleted file mode 100644 index 88bf82d16..000000000 --- a/listings/ch13-functional-features/listing-13-26/Cargo.lock +++ /dev/null @@ -1,6 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -[[package]] -name = "minigrep" -version = "0.1.0" - diff --git a/listings/ch13-functional-features/listing-13-26/Cargo.toml b/listings/ch13-functional-features/listing-13-26/Cargo.toml deleted file mode 100644 index 64c2a3f52..000000000 --- a/listings/ch13-functional-features/listing-13-26/Cargo.toml +++ /dev/null @@ -1,6 +0,0 @@ -[package] -name = "minigrep" -version = "0.1.0" -edition = "2021" - -[dependencies] diff --git a/listings/ch13-functional-features/listing-13-26/src/lib.rs b/listings/ch13-functional-features/listing-13-26/src/lib.rs deleted file mode 100644 index 5127c0a6f..000000000 --- a/listings/ch13-functional-features/listing-13-26/src/lib.rs +++ /dev/null @@ -1,109 +0,0 @@ -use std::env; -use std::error::Error; -use std::fs; - -pub struct Config { - pub query: String, - pub filename: String, - pub case_sensitive: bool, -} - -// ANCHOR: here -impl Config { - pub fn new( - mut args: impl Iterator, - ) -> Result { - // --snip-- - // ANCHOR_END: here - if args.len() < 3 { - return Err("not enough arguments"); - } - - let query = args[1].clone(); - let filename = args[2].clone(); - - let case_sensitive = env::var("CASE_INSENSITIVE").is_err(); - - Ok(Config { - query, - filename, - case_sensitive, - }) - } -} - -pub fn run(config: Config) -> Result<(), Box> { - let contents = fs::read_to_string(config.filename)?; - - let results = if config.case_sensitive { - search(&config.query, &contents) - } else { - search_case_insensitive(&config.query, &contents) - }; - - for line in results { - println!("{}", line); - } - - Ok(()) -} - -pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> { - let mut results = Vec::new(); - - for line in contents.lines() { - if line.contains(query) { - results.push(line); - } - } - - results -} - -pub fn search_case_insensitive<'a>( - query: &str, - contents: &'a str, -) -> Vec<&'a str> { - let query = query.to_lowercase(); - let mut results = Vec::new(); - - for line in contents.lines() { - if line.to_lowercase().contains(&query) { - results.push(line); - } - } - - results -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn case_sensitive() { - let query = "duct"; - let contents = "\ -Rust: -safe, fast, productive. -Pick three. -Duct tape."; - - assert_eq!(vec!["safe, fast, productive."], search(query, contents)); - } - - #[test] - fn case_insensitive() { - let query = "rUsT"; - let contents = "\ -Rust: -safe, fast, productive. -Pick three. -Trust me."; - - assert_eq!( - vec!["Rust:", "Trust me."], - search_case_insensitive(query, contents) - ); - } -} diff --git a/listings/ch13-functional-features/listing-13-27/Cargo.lock b/listings/ch13-functional-features/listing-13-27/Cargo.lock deleted file mode 100644 index 88bf82d16..000000000 --- a/listings/ch13-functional-features/listing-13-27/Cargo.lock +++ /dev/null @@ -1,6 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -[[package]] -name = "minigrep" -version = "0.1.0" - diff --git a/listings/ch13-functional-features/listing-13-27/Cargo.toml b/listings/ch13-functional-features/listing-13-27/Cargo.toml deleted file mode 100644 index 64c2a3f52..000000000 --- a/listings/ch13-functional-features/listing-13-27/Cargo.toml +++ /dev/null @@ -1,6 +0,0 @@ -[package] -name = "minigrep" -version = "0.1.0" -edition = "2021" - -[dependencies] diff --git a/listings/ch13-functional-features/listing-13-27/src/lib.rs b/listings/ch13-functional-features/listing-13-27/src/lib.rs deleted file mode 100644 index 8ffad682f..000000000 --- a/listings/ch13-functional-features/listing-13-27/src/lib.rs +++ /dev/null @@ -1,113 +0,0 @@ -use std::env; -use std::error::Error; -use std::fs; - -pub struct Config { - pub query: String, - pub filename: String, - pub case_sensitive: bool, -} - -// ANCHOR: here -impl Config { - pub fn new( - mut args: impl Iterator, - ) -> Result { - args.next(); - - let query = match args.next() { - Some(arg) => arg, - None => return Err("Didn't get a query string"), - }; - - let filename = match args.next() { - Some(arg) => arg, - None => return Err("Didn't get a file name"), - }; - - let case_sensitive = env::var("CASE_INSENSITIVE").is_err(); - - Ok(Config { - query, - filename, - case_sensitive, - }) - } -} -// ANCHOR_END: here - -pub fn run(config: Config) -> Result<(), Box> { - let contents = fs::read_to_string(config.filename)?; - - let results = if config.case_sensitive { - search(&config.query, &contents) - } else { - search_case_insensitive(&config.query, &contents) - }; - - for line in results { - println!("{}", line); - } - - Ok(()) -} - -pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> { - let mut results = Vec::new(); - - for line in contents.lines() { - if line.contains(query) { - results.push(line); - } - } - - results -} - -pub fn search_case_insensitive<'a>( - query: &str, - contents: &'a str, -) -> Vec<&'a str> { - let query = query.to_lowercase(); - let mut results = Vec::new(); - - for line in contents.lines() { - if line.to_lowercase().contains(&query) { - results.push(line); - } - } - - results -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn case_sensitive() { - let query = "duct"; - let contents = "\ -Rust: -safe, fast, productive. -Pick three. -Duct tape."; - - assert_eq!(vec!["safe, fast, productive."], search(query, contents)); - } - - #[test] - fn case_insensitive() { - let query = "rUsT"; - let contents = "\ -Rust: -safe, fast, productive. -Pick three. -Trust me."; - - assert_eq!( - vec!["Rust:", "Trust me."], - search_case_insensitive(query, contents) - ); - } -} diff --git a/listings/ch13-functional-features/listing-13-29/Cargo.lock b/listings/ch13-functional-features/listing-13-29/Cargo.lock deleted file mode 100644 index 88bf82d16..000000000 --- a/listings/ch13-functional-features/listing-13-29/Cargo.lock +++ /dev/null @@ -1,6 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -[[package]] -name = "minigrep" -version = "0.1.0" - diff --git a/listings/ch13-functional-features/listing-13-29/Cargo.toml b/listings/ch13-functional-features/listing-13-29/Cargo.toml deleted file mode 100644 index 64c2a3f52..000000000 --- a/listings/ch13-functional-features/listing-13-29/Cargo.toml +++ /dev/null @@ -1,6 +0,0 @@ -[package] -name = "minigrep" -version = "0.1.0" -edition = "2021" - -[dependencies] diff --git a/listings/ch13-functional-features/listing-13-29/src/lib.rs b/listings/ch13-functional-features/listing-13-29/src/lib.rs deleted file mode 100644 index 3ba2cdeec..000000000 --- a/listings/ch13-functional-features/listing-13-29/src/lib.rs +++ /dev/null @@ -1,108 +0,0 @@ -use std::env; -use std::error::Error; -use std::fs; - -pub struct Config { - pub query: String, - pub filename: String, - pub case_sensitive: bool, -} - -impl Config { - pub fn new( - mut args: impl Iterator, - ) -> Result { - args.next(); - - let query = match args.next() { - Some(arg) => arg, - None => return Err("Didn't get a query string"), - }; - - let filename = match args.next() { - Some(arg) => arg, - None => return Err("Didn't get a file name"), - }; - - let case_sensitive = env::var("CASE_INSENSITIVE").is_err(); - - Ok(Config { - query, - filename, - case_sensitive, - }) - } -} - -pub fn run(config: Config) -> Result<(), Box> { - let contents = fs::read_to_string(config.filename)?; - - let results = if config.case_sensitive { - search(&config.query, &contents) - } else { - search_case_insensitive(&config.query, &contents) - }; - - for line in results { - println!("{}", line); - } - - Ok(()) -} - -// ANCHOR: here -pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> { - contents - .lines() - .filter(|line| line.contains(query)) - .collect() -} -// ANCHOR_END: here - -pub fn search_case_insensitive<'a>( - query: &str, - contents: &'a str, -) -> Vec<&'a str> { - let query = query.to_lowercase(); - let mut results = Vec::new(); - - for line in contents.lines() { - if line.to_lowercase().contains(&query) { - results.push(line); - } - } - - results -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn case_sensitive() { - let query = "duct"; - let contents = "\ -Rust: -safe, fast, productive. -Pick three. -Duct tape."; - - assert_eq!(vec!["safe, fast, productive."], search(query, contents)); - } - - #[test] - fn case_insensitive() { - let query = "rUsT"; - let contents = "\ -Rust: -safe, fast, productive. -Pick three. -Trust me."; - - assert_eq!( - vec!["Rust:", "Trust me."], - search_case_insensitive(query, contents) - ); - } -} diff --git a/listings/ch13-functional-features/no-listing-01-failing-cacher-test/Cargo.lock b/listings/ch13-functional-features/no-listing-01-failing-cacher-test/Cargo.lock deleted file mode 100644 index e090432bc..000000000 --- a/listings/ch13-functional-features/no-listing-01-failing-cacher-test/Cargo.lock +++ /dev/null @@ -1,6 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -[[package]] -name = "cacher" -version = "0.1.0" - diff --git a/listings/ch13-functional-features/no-listing-01-failing-cacher-test/Cargo.toml b/listings/ch13-functional-features/no-listing-01-failing-cacher-test/Cargo.toml deleted file mode 100644 index 6b81ef92b..000000000 --- a/listings/ch13-functional-features/no-listing-01-failing-cacher-test/Cargo.toml +++ /dev/null @@ -1,6 +0,0 @@ -[package] -name = "cacher" -version = "0.1.0" -edition = "2021" - -[dependencies] diff --git a/listings/ch13-functional-features/no-listing-01-failing-cacher-test/output.txt b/listings/ch13-functional-features/no-listing-01-failing-cacher-test/output.txt deleted file mode 100644 index c9bda8c11..000000000 --- a/listings/ch13-functional-features/no-listing-01-failing-cacher-test/output.txt +++ /dev/null @@ -1,23 +0,0 @@ -$ cargo test - Compiling cacher v0.1.0 (file:///projects/cacher) - Finished test [unoptimized + debuginfo] target(s) in 0.72s - Running unittests (target/debug/deps/cacher-074d7c200c000afa) - -running 1 test -test tests::call_with_different_values ... FAILED - -failures: - ----- tests::call_with_different_values stdout ---- -thread 'main' panicked at 'assertion failed: `(left == right)` - left: `1`, - right: `2`', src/lib.rs:43:9 -note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace - - -failures: - tests::call_with_different_values - -test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s - -error: test failed, to rerun pass '--lib' diff --git a/listings/ch13-functional-features/no-listing-01-failing-cacher-test/src/lib.rs b/listings/ch13-functional-features/no-listing-01-failing-cacher-test/src/lib.rs deleted file mode 100644 index e7d677d0e..000000000 --- a/listings/ch13-functional-features/no-listing-01-failing-cacher-test/src/lib.rs +++ /dev/null @@ -1,47 +0,0 @@ -struct Cacher -where - T: Fn(u32) -> u32, -{ - calculation: T, - value: Option, -} - -impl Cacher -where - T: Fn(u32) -> u32, -{ - fn new(calculation: T) -> Cacher { - Cacher { - calculation, - value: None, - } - } - - fn value(&mut self, arg: u32) -> u32 { - match self.value { - Some(v) => v, - None => { - let v = (self.calculation)(arg); - self.value = Some(v); - v - } - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - // ANCHOR: here - #[test] - fn call_with_different_values() { - let mut c = Cacher::new(|a| a); - - let v1 = c.value(1); - let v2 = c.value(2); - - assert_eq!(v2, 2); - } - // ANCHOR_END: here -} diff --git a/listings/ch13-functional-features/no-listing-02-functions-cant-capture/Cargo.lock b/listings/ch13-functional-features/no-listing-02-functions-cant-capture/Cargo.lock deleted file mode 100644 index a96532add..000000000 --- a/listings/ch13-functional-features/no-listing-02-functions-cant-capture/Cargo.lock +++ /dev/null @@ -1,6 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -[[package]] -name = "equal-to-x" -version = "0.1.0" - diff --git a/listings/ch13-functional-features/no-listing-02-functions-cant-capture/Cargo.toml b/listings/ch13-functional-features/no-listing-02-functions-cant-capture/Cargo.toml deleted file mode 100644 index ca73ff73d..000000000 --- a/listings/ch13-functional-features/no-listing-02-functions-cant-capture/Cargo.toml +++ /dev/null @@ -1,6 +0,0 @@ -[package] -name = "equal-to-x" -version = "0.1.0" -edition = "2021" - -[dependencies] diff --git a/listings/ch13-functional-features/no-listing-02-functions-cant-capture/output.txt b/listings/ch13-functional-features/no-listing-02-functions-cant-capture/output.txt deleted file mode 100644 index 3cf991516..000000000 --- a/listings/ch13-functional-features/no-listing-02-functions-cant-capture/output.txt +++ /dev/null @@ -1,12 +0,0 @@ -$ cargo run - Compiling equal-to-x v0.1.0 (file:///projects/equal-to-x) -error[E0434]: can't capture dynamic environment in a fn item - --> src/main.rs:5:14 - | -5 | z == x - | ^ - | - = help: use the `|| { ... }` closure form instead - -For more information about this error, try `rustc --explain E0434`. -error: could not compile `equal-to-x` due to previous error diff --git a/listings/ch13-functional-features/no-listing-02-functions-cant-capture/src/main.rs b/listings/ch13-functional-features/no-listing-02-functions-cant-capture/src/main.rs deleted file mode 100644 index 1b5d2b930..000000000 --- a/listings/ch13-functional-features/no-listing-02-functions-cant-capture/src/main.rs +++ /dev/null @@ -1,11 +0,0 @@ -fn main() { - let x = 4; - - fn equal_to_x(z: i32) -> bool { - z == x - } - - let y = 4; - - assert!(equal_to_x(y)); -} diff --git a/listings/ch13-functional-features/no-listing-03-move-closures/Cargo.lock b/listings/ch13-functional-features/no-listing-03-move-closures/Cargo.lock deleted file mode 100644 index a96532add..000000000 --- a/listings/ch13-functional-features/no-listing-03-move-closures/Cargo.lock +++ /dev/null @@ -1,6 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -[[package]] -name = "equal-to-x" -version = "0.1.0" - diff --git a/listings/ch13-functional-features/no-listing-03-move-closures/Cargo.toml b/listings/ch13-functional-features/no-listing-03-move-closures/Cargo.toml deleted file mode 100644 index ca73ff73d..000000000 --- a/listings/ch13-functional-features/no-listing-03-move-closures/Cargo.toml +++ /dev/null @@ -1,6 +0,0 @@ -[package] -name = "equal-to-x" -version = "0.1.0" -edition = "2021" - -[dependencies] diff --git a/listings/ch13-functional-features/no-listing-03-move-closures/output.txt b/listings/ch13-functional-features/no-listing-03-move-closures/output.txt deleted file mode 100644 index a04abac4f..000000000 --- a/listings/ch13-functional-features/no-listing-03-move-closures/output.txt +++ /dev/null @@ -1,20 +0,0 @@ -$ cargo run - Compiling equal-to-x v0.1.0 (file:///projects/equal-to-x) -error[E0382]: borrow of moved value: `x` - --> src/main.rs:6:40 - | -2 | let x = vec![1, 2, 3]; - | - move occurs because `x` has type `Vec`, which does not implement the `Copy` trait -3 | -4 | let equal_to_x = move |z| z == x; - | -------- - variable moved due to use in closure - | | - | value moved into closure here -5 | -6 | println!("can't use x here: {:?}", x); - | ^ value borrowed here after move - | - = note: this error originates in the macro `$crate::format_args_nl` (in Nightly builds, run with -Z macro-backtrace for more info) - -For more information about this error, try `rustc --explain E0382`. -error: could not compile `equal-to-x` due to previous error diff --git a/listings/ch13-functional-features/no-listing-03-move-closures/src/main.rs b/listings/ch13-functional-features/no-listing-03-move-closures/src/main.rs deleted file mode 100644 index 19d477608..000000000 --- a/listings/ch13-functional-features/no-listing-03-move-closures/src/main.rs +++ /dev/null @@ -1,11 +0,0 @@ -fn main() { - let x = vec![1, 2, 3]; - - let equal_to_x = move |z| z == x; - - println!("can't use x here: {:?}", x); - - let y = vec![1, 2, 3]; - - assert!(equal_to_x(y)); -} diff --git a/listings/ch19-advanced-features/listing-13-21-reproduced/Cargo.lock b/listings/ch19-advanced-features/listing-13-21-reproduced/Cargo.lock deleted file mode 100644 index 58b70c5b7..000000000 --- a/listings/ch19-advanced-features/listing-13-21-reproduced/Cargo.lock +++ /dev/null @@ -1,6 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -[[package]] -name = "counter" -version = "0.1.0" - diff --git a/listings/ch19-advanced-features/listing-13-21-reproduced/Cargo.toml b/listings/ch19-advanced-features/listing-13-21-reproduced/Cargo.toml deleted file mode 100644 index 9e103f3eb..000000000 --- a/listings/ch19-advanced-features/listing-13-21-reproduced/Cargo.toml +++ /dev/null @@ -1,6 +0,0 @@ -[package] -name = "counter" -version = "0.1.0" -edition = "2021" - -[dependencies] diff --git a/listings/ch13-functional-features/listing-13-21/Cargo.lock b/listings/ch19-advanced-features/no-listing-22-iterator-on-counter/Cargo.lock similarity index 100% rename from listings/ch13-functional-features/listing-13-21/Cargo.lock rename to listings/ch19-advanced-features/no-listing-22-iterator-on-counter/Cargo.lock diff --git a/listings/ch13-functional-features/listing-13-21/Cargo.toml b/listings/ch19-advanced-features/no-listing-22-iterator-on-counter/Cargo.toml similarity index 100% rename from listings/ch13-functional-features/listing-13-21/Cargo.toml rename to listings/ch19-advanced-features/no-listing-22-iterator-on-counter/Cargo.toml diff --git a/listings/ch19-advanced-features/listing-13-21-reproduced/src/lib.rs b/listings/ch19-advanced-features/no-listing-22-iterator-on-counter/src/lib.rs similarity index 100% rename from listings/ch19-advanced-features/listing-13-21-reproduced/src/lib.rs rename to listings/ch19-advanced-features/no-listing-22-iterator-on-counter/src/lib.rs diff --git a/nostarch/chapter13.md b/nostarch/chapter13.md index f73dc14d8..54e6544b0 100644 --- a/nostarch/chapter13.md +++ b/nostarch/chapter13.md @@ -57,8 +57,7 @@ company’s inventory is represented by an `Inventory` struct that has a field named `shirts` that contains a `Vec` representing the shirts currently in stock. The method `shirt_giveaway` defined on `Inventory` gets the optional shirt color preference of the person getting the free shirt, and -returns the shirt color the person will get. This setup is shown in Listing -13-x: +returns the shirt color the person will get. This is shown in Listing 13-1: Filename: src/main.rs @@ -117,7 +116,7 @@ fn main() { } ``` -Listing 13-x: Framework of the shirt company giveaway situation +Listing 13-1: Shirt company giveaway The `store` defined in `main` has two blue shirts and one red shirt in stock. Then it calls the `giveaway` method for a user with a preference for a red @@ -135,13 +134,14 @@ The user with preference None gets Blue Again, this code could be implemented in many ways, but this way uses concepts you’ve already learned, except for the body of the `giveaway` method that uses a closure. The `giveaway` method takes the user preference `Option` -and calls `unwrap_or_else` on it. The `unwrap_or_else` method on `Option` is -defined by the standard library. It takes one argument: a closure without any -arguments that returns a value `T` (the same type stored in the `Some` variant -of the `Option`, in this case, a `ShirtColor`). If the `Option` is the -`Some` variant, `unwrap_or_else` returns the value from within the `Some`. If -the `Option` is the `None` variant, `unwrap_or_else` calls the closure and -returns the value returned by the closure. +and calls `unwrap_or_else` on it. The `unwrap_or_else` method on +`Option` is defined by the standard library. +It takes one argument: a closure without any arguments that returns a value `T` +(the same type stored in the `Some` variant of the `Option`, in this case, a +`ShirtColor`). If the `Option` is the `Some` variant, `unwrap_or_else` +returns the value from within the `Some`. If the `Option` is the `None` +variant, `unwrap_or_else` calls the closure and returns the value returned by +the closure. This is interesting because we’ve passed a closure that calls `self.most_stocked()` on the current `Inventory` instance. The standard library @@ -171,7 +171,7 @@ needs closure type annotations too). As with variables, we can add type annotations if we want to increase explicitness and clarity at the cost of being more verbose than is strictly necessary. Annotating the types for a closure would look like the definition -shown in Listing 13-x. +shown in Listing 13-2. Filename: src/main.rs @@ -183,7 +183,7 @@ let expensive_closure = |num: u32| -> u32 { }; ``` -Listing 13-x: Adding optional type annotations of the parameter and return +Listing 13-2: Adding optional type annotations of the parameter and return value types in the closure With type annotations added, the syntax of closures looks more similar to the @@ -209,7 +209,7 @@ the closures is required for `add_one_v3` and `add_one_v4` to be able to compile because the types will be inferred from their usage. Closure definitions will have one concrete type inferred for each of their -parameters and for their return value. For instance, Listing 13-x shows the +parameters and for their return value. For instance, Listing 13-3 shows the definition of a short closure that just returns the value it receives as a parameter. This closure isn’t very useful except for the purposes of this example. Note that we haven’t added any type annotations to the definition: if @@ -225,7 +225,8 @@ let s = example_closure(String::from("hello")); let n = example_closure(5); ``` -Listing 13-x: Attempting to call a closure whose types are inferred with two different types +Listing 13-3: Attempting to call a closure whose types are inferred with two +different types The compiler gives us this error: @@ -252,7 +253,7 @@ immutably, borrowing mutably, and taking ownership. The closure will decide which of these to use based on what the body of the function does with the captured values. -Listing 13-x defines a closure that captures an immutable borrow to the vector +Listing 13-4 defines a closure that captures an immutable borrow to the vector named `list` because it only needs an immutable borrow to print the value. This example also illustrates that a variable can bind to a closure definition, and the closure can later be called by using the variable name and parentheses as @@ -273,7 +274,7 @@ fn main() { } ``` -Listing 13-x: Defining and calling a closure that captures an immutable borrow +Listing 13-4: Defining and calling a closure that captures an immutable borrow The `list` is still accessible by the code before the closure definition, after the closure definition but before the closure is called, and after the closure @@ -287,7 +288,7 @@ From closure: [1, 2, 3] After calling closure: [1, 2, 3] ``` -Next, Listing 13-x changes the closure definition to need a mutable borrow +Next, Listing 13-5 changes the closure definition to need a mutable borrow because the closure body adds an element to the `list` vector: Filename: src/main.rs @@ -304,7 +305,7 @@ fn main() { } ``` -Listing 13-x: Defining and calling a closure that captures a mutable borrow +Listing 13-5: Defining and calling a closure that captures a mutable borrow This code compiles, runs, and prints: @@ -356,7 +357,7 @@ traits, in an additive fashion: capture anything from their environment implement `Fn`. Let’s look at the definition of the `unwrap_or_else` method on `Option` that -we used in Listing 13-x: +we used in Listing 13-6: ``` impl Option { @@ -402,9 +403,11 @@ to see how that differs. It takes a closure that implements `FnMut`. The closure gets one argument, a reference to the current item in the slice being considered, and returns a value of type `K` that can be ordered. This function is useful when you want to sort a slice by a particular attribute of each item. -In Listing 13-x, we have a list of `Rectangle` instances and we use +In Listing 13-7, we have a list of `Rectangle` instances and we use `sort_by_key` to order them by their `width` attribute from low to high: +Filename: src/main.rs + ``` #[derive(Debug)] struct Rectangle { @@ -414,9 +417,18 @@ struct Rectangle { fn main() { let mut list = [ - Rectangle { width: 10, height: 1 }, - Rectangle { width: 3, height: 5 }, - Rectangle { width: 7, height: 12 }, + Rectangle { + width: 10, + height: 1, + }, + Rectangle { + width: 3, + height: 5, + }, + Rectangle { + width: 7, + height: 12, + }, ]; list.sort_by_key(|r| r.width); @@ -424,6 +436,9 @@ fn main() { } ``` +Listing 13-7: Using `sort_by_key` and a closure to sort a list of `Rectangle` +instances by their `width` value + This code prints: ``` @@ -448,9 +463,11 @@ the closure multiple times: once for each item in the slice. The closure `|r| r.width` doesn’t capture, mutate, or move out anything from its environment, so it meets the trait bound requirements. -In contrast, here’s an example of a closure that only implements `FnOnce` -because it moves a value out of the environment. The compiler won’t let us use -this closure with `sort_by_key`: +In contrast, Listing 13-8 shows an example of a closure that only implements +`FnOnce` because it moves a value out of the environment. The compiler won’t +let us use this closure with `sort_by_key`: + +Filename: src/main.rs ``` #[derive(Debug)] @@ -461,9 +478,18 @@ struct Rectangle { fn main() { let mut list = [ - Rectangle { width: 10, height: 1 }, - Rectangle { width: 3, height: 5 }, - Rectangle { width: 7, height: 12 }, + Rectangle { + width: 10, + height: 1, + }, + Rectangle { + width: 3, + height: 5, + }, + Rectangle { + width: 7, + height: 12, + }, ]; let mut sort_operations = vec![]; @@ -477,6 +503,8 @@ fn main() { } ``` +Listing 13-8: Attempting to use an `FnOnce` closure with `sort_by_key` + This is a contrived, convoluted way (that doesn’t work) to try and count the number of times `sort_by_key` gets called when sorting `list`. This code attempts to do this counting by pushing `value`, a `String` from the closure’s @@ -491,17 +519,17 @@ implement `FnMut`: ``` error[E0507]: cannot move out of `value`, a captured variable in an `FnMut` closure - --> src/main.rs:18:30 + --> src/main.rs:27:30 | -15 | let value = String::from("by key called"); +24 | let value = String::from("by key called"); | ----- captured outer variable -16 | -17 | list.sort_by_key(|r| { +25 | +26 | list.sort_by_key(|r| { | ______________________- -18 | | sort_operations.push(value); +27 | | sort_operations.push(value); | | ^^^^^ move occurs because `value` has type `String`, which does not implement the `Copy` trait -19 | | r.width -20 | | }); +28 | | r.width +29 | | }); | |_____- captured by this `FnMut` closure ``` @@ -510,9 +538,11 @@ environment. To fix this, we need to change the closure body so that it doesn’ move values out of the environment. If we’re interested in the number of times `sort_by_key` is called, keeping a counter in the environment and incrementing its value in the closure body is a more straightforward way to calculate that. -This closure works with `sort_by_key` because it is only capturing a mutable -reference to the `num_sort_operations` counter and can therefore be called more -than once: +The closure in Listing 13-9 works with `sort_by_key` because it is only +capturing a mutable reference to the `num_sort_operations` counter and can +therefore be called more than once: + +Filename: src/main.rs ``` #[derive(Debug)] @@ -523,9 +553,18 @@ struct Rectangle { fn main() { let mut list = [ - Rectangle { width: 10, height: 1 }, - Rectangle { width: 3, height: 5 }, - Rectangle { width: 7, height: 12 }, + Rectangle { + width: 10, + height: 1, + }, + Rectangle { + width: 3, + height: 5, + }, + Rectangle { + width: 7, + height: 12, + }, ]; let mut num_sort_operations = 0; @@ -537,6 +576,8 @@ fn main() { } ``` +Listing 13-9: Using an `FnMut` closure with `sort_by_key` is allowed + The `Fn` traits are important when defining or using functions or types that make use of closures. The next section discusses iterators, and many iterator methods take closure arguments. Keep these details of closures in mind as we @@ -551,7 +592,7 @@ have to reimplement that logic yourself. In Rust, iterators are *lazy*, meaning they have no effect until you call methods that consume the iterator to use it up. For example, the code in -Listing 13-13 creates an iterator over the items in the vector `v1` by calling +Listing 13-10 creates an iterator over the items in the vector `v1` by calling the `iter` method defined on `Vec`. This code by itself doesn’t do anything useful. @@ -561,14 +602,14 @@ let v1 = vec![1, 2, 3]; let v1_iter = v1.iter(); ``` -Listing 13-13: Creating an iterator +Listing 13-10: Creating an iterator Once we’ve created an iterator, we can use it in a variety of ways. In Listing 3-5 in Chapter 3, we iterated over an array using a `for` loop to execute some code on each of its items. Under the hood this implicitly created and then consumed an iterator, but we glossed over how exactly that works until now. -The example in Listing 13-14 separates the creation of the iterator from the +The example in Listing 13-11 separates the creation of the iterator from the use of the iterator in the `for` loop. The iterator is stored in the `v1_iter` variable, and no iteration takes place at that time. When the `for` loop is called using the iterator in `v1_iter`, each element in the iterator is used in @@ -584,7 +625,7 @@ for val in v1_iter { } ``` -Listing 13-14: Using an iterator in a `for` loop +Listing 13-11: Using an iterator in a `for` loop In languages that don’t have iterators provided by their standard libraries, you would likely write this same functionality by starting a variable at index @@ -624,7 +665,7 @@ The `Iterator` trait only requires implementors to define one method: the `next` method, which returns one item of the iterator at a time wrapped in `Some` and, when iteration is over, returns `None`. -We can call the `next` method on iterators directly; Listing 13-15 demonstrates +We can call the `next` method on iterators directly; Listing 13-12 demonstrates what values are returned from repeated calls to `next` on the iterator created from the vector. @@ -644,7 +685,7 @@ fn iterator_demonstration() { } ``` -Listing 13-15: Calling the `next` method on an iterator +Listing 13-12: Calling the `next` method on an iterator Note that we needed to make `v1_iter` mutable: calling the `next` method on an iterator changes internal state that the iterator uses to keep track of where @@ -673,7 +714,7 @@ Methods that call `next` are called *consuming adaptors*, because calling them uses up the iterator. One example is the `sum` method, which takes ownership of the iterator and iterates through the items by repeatedly calling `next`, thus consuming the iterator. As it iterates through, it adds each item to a running -total and returns the total when iteration is complete. Listing 13-16 has a +total and returns the total when iteration is complete. Listing 13-13 has a test illustrating a use of the `sum` method: Filename: src/lib.rs @@ -691,7 +732,8 @@ fn iterator_sum() { } ``` -Listing 13-16: Calling the `sum` method to get the total of all items in the iterator +Listing 13-13: Calling the `sum` method to get the total of all items in the +iterator We aren’t allowed to use `v1_iter` after the call to `sum` because `sum` takes ownership of the iterator we call it on. @@ -704,7 +746,7 @@ multiple calls to iterator adaptors to perform complex actions in a readable way. But because all iterators are lazy, you have to call one of the consuming adaptor methods to get results from calls to iterator adaptors. -Listing 13-17 shows an example of calling the iterator adaptor method `map`, +Listing 13-14 shows an example of calling the iterator adaptor method `map`, which takes a closure to call on each item to produce a new iterator. The closure here creates a new iterator in which each item from the vector has been incremented by 1. However, this code produces a warning: @@ -717,8 +759,7 @@ let v1: Vec = vec![1, 2, 3]; v1.iter().map(|x| x + 1); ``` -Listing 13-17: Calling the iterator adaptor `map` to -create a new iterator +Listing 13-14: Calling the iterator adaptor `map` to create a new iterator The warning we get is this: @@ -733,7 +774,7 @@ warning: unused `Map` that must be used = note: iterators are lazy and do nothing unless consumed ``` -The code in Listing 13-17 doesn’t do anything; the closure we’ve specified +The code in Listing 13-14 doesn’t do anything; the closure we’ve specified never gets called. The warning reminds us why: iterator adaptors are lazy, and we need to consume the iterator here. @@ -741,7 +782,7 @@ To fix this and consume the iterator, we’ll use the `collect` method, which we used in Chapter 12 with `env::args` in Listing 12-1. This method consumes the iterator and collects the resulting values into a collection data type. -In Listing 13-18, we collect the results of iterating over the iterator that’s +In Listing 13-15, we collect the results of iterating over the iterator that’s returned from the call to `map` into a vector. This vector will end up containing each item from the original vector incremented by 1. @@ -755,7 +796,7 @@ let v2: Vec<_> = v1.iter().map(|x| x + 1).collect(); assert_eq!(v2, vec![2, 3, 4]); ``` -Listing 13-18: Calling the `map` method to create a new iterator and then +Listing 13-15: Calling the `map` method to create a new iterator and then calling the `collect` method to consume the new iterator and create a vector Because `map` takes a closure, we can specify any operation we want to perform @@ -772,7 +813,7 @@ the iterator and returns a Boolean. If the closure returns `true`, the value will be included in the iterator produced by `filter`. If the closure returns `false`, the value won’t be included in the resulting iterator. -In Listing 13-19, we use `filter` with a closure that captures the `shoe_size` +In Listing 13-16, we use `filter` with a closure that captures the `shoe_size` variable from its environment to iterate over a collection of `Shoe` struct instances. It will return only shoes that are the specified size. @@ -829,7 +870,7 @@ mod tests { } ``` -Listing 13-19: Using the `filter` method with a closure that captures +Listing 13-16: Using the `filter` method with a closure that captures `shoe_size` The `shoes_in_size` function takes ownership of a vector of shoes and a shoe @@ -860,7 +901,7 @@ concise. Let’s look at how iterators can improve our implementation of the In Listing 12-6, we added code that took a slice of `String` values and created an instance of the `Config` struct by indexing into the slice and cloning the -values, allowing the `Config` struct to own those values. In Listing 13-24, +values, allowing the `Config` struct to own those values. In Listing 13-17, we’ve reproduced the implementation of the `Config::new` function as it was in Listing 12-23: @@ -887,7 +928,7 @@ impl Config { } ``` -Listing 13-24: Reproduction of the `Config::new` function from Listing 12-23 +Listing 13-17: Reproduction of the `Config::new` function from Listing 12-23 At the time, we said not to worry about the inefficient `clone` calls because we would remove them in the future. Well, that time is now! @@ -927,7 +968,7 @@ fn main() { ``` We’ll change the start of the `main` function that we had in Listing 12-24 to -the code in Listing 13-25. This won’t compile until we update `Config::new` as +the code in Listing 13-18. This won’t compile until we update `Config::new` as well. Filename: src/main.rs @@ -943,7 +984,7 @@ fn main() { } ``` -Listing 13-25: Passing the return value of `env::args` to `Config::new` +Listing 13-18: Passing the return value of `env::args` to `Config::new` The `env::args` function returns an iterator! Rather than collecting the iterator values into a vector and then passing a slice to `Config::new`, now @@ -952,7 +993,7 @@ we’re passing ownership of the iterator returned from `env::args` to Next, we need to update the definition of `Config::new`. In your I/O project’s *src/lib.rs* file, let’s change the signature of `Config::new` to look like -Listing 13-26. This still won’t compile because we need to update the function +Listing 13-19. This still won’t compile because we need to update the function body. Filename: src/lib.rs @@ -965,7 +1006,7 @@ impl Config { // --snip-- ``` -Listing 13-26: Updating the signature of `Config::new` to expect an iterator +Listing 13-19: Updating the signature of `Config::new` to expect an iterator The standard library documentation for the `env::args` function shows that the type of the iterator it returns is `std::env::Args`, and that type implements @@ -985,7 +1026,7 @@ iterating over it, we can add the `mut` keyword into the specification of the #### Using `Iterator` Trait Methods Instead of Indexing Next, we’ll fix the body of `Config::new`. Because `args` implements the -`Iterator` trait, we know we can call the `next` method on it! Listing 13-27 +`Iterator` trait, we know we can call the `next` method on it! Listing 13-20 updates the code from Listing 12-23 to use the `next` method: Filename: src/lib.rs @@ -1018,7 +1059,7 @@ impl Config { } ``` -Listing 13-27: Changing the body of `Config::new` to use iterator methods +Listing 13-20: Changing the body of `Config::new` to use iterator methods Remember that the first value in the return value of `env::args` is the name of the program. We want to ignore that and get to the next value, so first we call @@ -1031,7 +1072,7 @@ the same thing for the `filename` value. ### Making Code Clearer with Iterator Adaptors We can also take advantage of iterators in the `search` function in our I/O -project, which is reproduced here in Listing 13-28 as it was in Listing 12-19: +project, which is reproduced here in Listing 13-21 as it was in Listing 12-19: Filename: src/lib.rs @@ -1049,14 +1090,14 @@ pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> { } ``` -Listing 13-28: The implementation of the `search` function from Listing 12-19 +Listing 13-21: The implementation of the `search` function from Listing 12-19 We can write this code in a more concise way using iterator adaptor methods. Doing so also lets us avoid having a mutable intermediate `results` vector. The functional programming style prefers to minimize the amount of mutable state to make code clearer. Removing the mutable state might enable a future enhancement to make searching happen in parallel, because we wouldn’t have to manage -concurrent access to the `results` vector. Listing 13-29 shows this change: +concurrent access to the `results` vector. Listing 13-22 shows this change: Filename: src/lib.rs @@ -1069,20 +1110,20 @@ pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> { } ``` -Listing 13-29: Using iterator adaptor methods in the implementation of the +Listing 13-22: Using iterator adaptor methods in the implementation of the `search` function Recall that the purpose of the `search` function is to return all lines in `contents` that contain the `query`. Similar to the `filter` example in Listing -13-19, this code uses the `filter` adaptor to keep only the lines that +13-16, this code uses the `filter` adaptor to keep only the lines that `line.contains(query)` returns `true` for. We then collect the matching lines into another vector with `collect`. Much simpler! Feel free to make the same change to use iterator methods in the `search_case_insensitive` function as well. The next logical question is which style you should choose in your own code and -why: the original implementation in Listing 13-28 or the version using -iterators in Listing 13-29. Most Rust programmers prefer to use the iterator +why: the original implementation in Listing 13-21 or the version using +iterators in Listing 13-22. Most Rust programmers prefer to use the iterator style. It’s a bit tougher to get the hang of at first, but once you get a feel for the various iterator adaptors and what they do, iterators can be easier to understand. Instead of fiddling with the various bits of looping and building diff --git a/src/ch13-01-closures.md b/src/ch13-01-closures.md index 5f40a2f83..88f578eb2 100644 --- a/src/ch13-01-closures.md +++ b/src/ch13-01-closures.md @@ -29,8 +29,7 @@ company’s inventory is represented by an `Inventory` struct that has a field named `shirts` that contains a `Vec` representing the shirts currently in stock. The method `shirt_giveaway` defined on `Inventory` gets the optional shirt color preference of the person getting the free shirt, and -returns the shirt color the person will get. This setup is shown in Listing -13-x: +returns the shirt color the person will get. This is shown in Listing 13-1: Filename: src/main.rs @@ -38,8 +37,7 @@ returns the shirt color the person will get. This setup is shown in Listing {{#rustdoc_include ../listings/ch13-functional-features/listing-13-01/src/main.rs}} ``` -Listing 13-x: Framework of the shirt company giveaway -situation +Listing 13-1: Shirt company giveaway The `store` defined in `main` has two blue shirts and one red shirt in stock. Then it calls the `giveaway` method for a user with a preference for a red @@ -89,15 +87,15 @@ needs closure type annotations too). As with variables, we can add type annotations if we want to increase explicitness and clarity at the cost of being more verbose than is strictly necessary. Annotating the types for a closure would look like the definition -shown in Listing 13-x. +shown in Listing 13-2. Filename: src/main.rs ```rust -{{#rustdoc_include ../listings/ch13-functional-features/listing-13-07/src/main.rs:here}} +{{#rustdoc_include ../listings/ch13-functional-features/listing-13-02/src/main.rs:here}} ``` -Listing 13-x: Adding optional type annotations of the +Listing 13-2: Adding optional type annotations of the parameter and return value types in the closure With type annotations added, the syntax of closures looks more similar to the @@ -123,7 +121,7 @@ the closures is required for `add_one_v3` and `add_one_v4` to be able to compile because the types will be inferred from their usage. Closure definitions will have one concrete type inferred for each of their -parameters and for their return value. For instance, Listing 13-x shows the +parameters and for their return value. For instance, Listing 13-3 shows the definition of a short closure that just returns the value it receives as a parameter. This closure isn’t very useful except for the purposes of this example. Note that we haven’t added any type annotations to the definition: if @@ -133,16 +131,16 @@ first time and a `u32` the second time, we’ll get an error. Filename: src/main.rs ```rust,ignore,does_not_compile -{{#rustdoc_include ../listings/ch13-functional-features/listing-13-08/src/main.rs:here}} +{{#rustdoc_include ../listings/ch13-functional-features/listing-13-03/src/main.rs:here}} ``` -Listing 13-x: Attempting to call a closure whose types +Listing 13-3: Attempting to call a closure whose types are inferred with two different types The compiler gives us this error: ```console -{{#include ../listings/ch13-functional-features/listing-13-08/output.txt}} +{{#include ../listings/ch13-functional-features/listing-13-03/output.txt}} ``` The first time we call `example_closure` with the `String` value, the compiler @@ -158,7 +156,7 @@ immutably, borrowing mutably, and taking ownership. The closure will decide which of these to use based on what the body of the function does with the captured values. -Listing 13-x defines a closure that captures an immutable borrow to the vector +Listing 13-4 defines a closure that captures an immutable borrow to the vector named `list` because it only needs an immutable borrow to print the value. This example also illustrates that a variable can bind to a closure definition, and the closure can later be called by using the variable name and parentheses as @@ -167,19 +165,10 @@ if the variable name were a function name: Filename: src/main.rs ```rust -fn main() { - let list = vec![1, 2, 3]; - println!("Before defining closure: {:?}", list); - - let only_borrows = || println!("From closure: {:?}", list); - - println!("Before calling closure: {:?}", list); - only_borrows(); - println!("After calling closure: {:?}", list); -} +{{#rustdoc_include ../listings/ch13-functional-features/listing-13-04/src/main.rs}} ``` -Listing 13-x: Defining and calling a closure that +Listing 13-4: Defining and calling a closure that captures an immutable borrow The `list` is still accessible by the code before the closure definition, after @@ -188,37 +177,25 @@ is called because we can have multiple immutable borrows of `list` at the same time. This code compiles, runs, and prints: ```console -Before defining closure: [1, 2, 3] -Before calling closure: [1, 2, 3] -From closure: [1, 2, 3] -After calling closure: [1, 2, 3] +{{#include ../listings/ch13-functional-features/listing-13-04/output.txt}} ``` -Next, Listing 13-x changes the closure definition to need a mutable borrow +Next, Listing 13-5 changes the closure definition to need a mutable borrow because the closure body adds an element to the `list` vector: Filename: src/main.rs ```rust -fn main() { - let mut list = vec![1, 2, 3]; - println!("Before defining closure: {:?}", list); - - let mut borrows_mutably = || list.push(7); - - borrows_mutably(); - println!("After calling closure: {:?}", list); -} +{{#rustdoc_include ../listings/ch13-functional-features/listing-13-05/src/main.rs}} ``` -Listing 13-x: Defining and calling a closure that +Listing 13-5: Defining and calling a closure that captures a mutable borrow This code compiles, runs, and prints: ```console -Before defining closure: [1, 2, 3] -After calling closure: [1, 2, 3, 7] +{{#include ../listings/ch13-functional-features/listing-13-05/output.txt}} ``` Note that there’s no longer a `println!` between the definition and the call of @@ -268,7 +245,7 @@ traits, in an additive fashion: capture anything from their environment implement `Fn`. Let’s look at the definition of the `unwrap_or_else` method on `Option` that -we used in Listing 13-x: +we used in Listing 13-6: ```rust,ignore impl Option { @@ -314,45 +291,22 @@ to see how that differs. It takes a closure that implements `FnMut`. The closure gets one argument, a reference to the current item in the slice being considered, and returns a value of type `K` that can be ordered. This function is useful when you want to sort a slice by a particular attribute of each item. -In Listing 13-x, we have a list of `Rectangle` instances and we use +In Listing 13-7, we have a list of `Rectangle` instances and we use `sort_by_key` to order them by their `width` attribute from low to high: +Filename: src/main.rs + ```rust -#[derive(Debug)] -struct Rectangle { - width: u32, - height: u32, -} - -fn main() { - let mut list = [ - Rectangle { width: 10, height: 1 }, - Rectangle { width: 3, height: 5 }, - Rectangle { width: 7, height: 12 }, - ]; - - list.sort_by_key(|r| r.width); - println!("{:#?}", list); -} +{{#rustdoc_include ../listings/ch13-functional-features/listing-13-07/src/main.rs}} ``` +Listing 13-7: Using `sort_by_key` and a closure to sort a +list of `Rectangle` instances by their `width` value + This code prints: ```console -[ - Rectangle { - width: 3, - height: 5, - }, - Rectangle { - width: 7, - height: 12, - }, - Rectangle { - width: 10, - height: 1, - }, -] +{{#include ../listings/ch13-functional-features/listing-13-07/output.txt}} ``` The reason `sort_by_key` is defined to take an `FnMut` closure is that it calls @@ -360,35 +314,19 @@ the closure multiple times: once for each item in the slice. The closure `|r| r.width` doesn’t capture, mutate, or move out anything from its environment, so it meets the trait bound requirements. -In contrast, here’s an example of a closure that only implements `FnOnce` -because it moves a value out of the environment. The compiler won’t let us use -this closure with `sort_by_key`: +In contrast, Listing 13-8 shows an example of a closure that only implements +`FnOnce` because it moves a value out of the environment. The compiler won’t +let us use this closure with `sort_by_key`: + +Filename: src/main.rs ```rust,ignore,does_not_compile -#[derive(Debug)] -struct Rectangle { - width: u32, - height: u32, -} - -fn main() { - let mut list = [ - Rectangle { width: 10, height: 1 }, - Rectangle { width: 3, height: 5 }, - Rectangle { width: 7, height: 12 }, - ]; - - let mut sort_operations = vec![]; - let value = String::from("by key called"); - - list.sort_by_key(|r| { - sort_operations.push(value); - r.width - }); - println!("{:#?}", list); -} +{{#rustdoc_include ../listings/ch13-functional-features/listing-13-08/src/main.rs}} ``` +Listing 13-8: Attempting to use an `FnOnce` closure with +`sort_by_key` + This is a contrived, convoluted way (that doesn’t work) to try and count the number of times `sort_by_key` gets called when sorting `list`. This code attempts to do this counting by pushing `value`, a `String` from the closure’s @@ -402,19 +340,7 @@ that `value` can’t be moved out of the closure because the closure must implement `FnMut`: ```console -error[E0507]: cannot move out of `value`, a captured variable in an `FnMut` closure - --> src/main.rs:18:30 - | -15 | let value = String::from("by key called"); - | ----- captured outer variable -16 | -17 | list.sort_by_key(|r| { - | ______________________- -18 | | sort_operations.push(value); - | | ^^^^^ move occurs because `value` has type `String`, which does not implement the `Copy` trait -19 | | r.width -20 | | }); - | |_____- captured by this `FnMut` closure +{{#include ../listings/ch13-functional-features/listing-13-08/output.txt}} ``` The error points to the line in the closure body that moves `value` out of the @@ -422,33 +348,19 @@ environment. To fix this, we need to change the closure body so that it doesn’ move values out of the environment. If we’re interested in the number of times `sort_by_key` is called, keeping a counter in the environment and incrementing its value in the closure body is a more straightforward way to calculate that. -This closure works with `sort_by_key` because it is only capturing a mutable -reference to the `num_sort_operations` counter and can therefore be called more -than once: +The closure in Listing 13-9 works with `sort_by_key` because it is only +capturing a mutable reference to the `num_sort_operations` counter and can +therefore be called more than once: + +Filename: src/main.rs ```rust -#[derive(Debug)] -struct Rectangle { - width: u32, - height: u32, -} - -fn main() { - let mut list = [ - Rectangle { width: 10, height: 1 }, - Rectangle { width: 3, height: 5 }, - Rectangle { width: 7, height: 12 }, - ]; - - let mut num_sort_operations = 0; - list.sort_by_key(|r| { - num_sort_operations += 1; - r.width - }); - println!("{:#?}, sorted in {num_sort_operations} operations", list); -} +{{#rustdoc_include ../listings/ch13-functional-features/listing-13-09/src/main.rs}} ``` +Listing 13-9: Using an `FnMut` closure with `sort_by_key` +is allowed + The `Fn` traits are important when defining or using functions or types that make use of closures. The next section discusses iterators, and many iterator methods take closure arguments. Keep these details of closures in mind as we diff --git a/src/ch13-02-iterators.md b/src/ch13-02-iterators.md index 4c5044693..f6a0485c9 100644 --- a/src/ch13-02-iterators.md +++ b/src/ch13-02-iterators.md @@ -7,32 +7,32 @@ have to reimplement that logic yourself. In Rust, iterators are *lazy*, meaning they have no effect until you call methods that consume the iterator to use it up. For example, the code in -Listing 13-13 creates an iterator over the items in the vector `v1` by calling +Listing 13-10 creates an iterator over the items in the vector `v1` by calling the `iter` method defined on `Vec`. This code by itself doesn’t do anything useful. ```rust -{{#rustdoc_include ../listings/ch13-functional-features/listing-13-13/src/main.rs:here}} +{{#rustdoc_include ../listings/ch13-functional-features/listing-13-10/src/main.rs:here}} ``` -Listing 13-13: Creating an iterator +Listing 13-10: Creating an iterator Once we’ve created an iterator, we can use it in a variety of ways. In Listing 3-5 in Chapter 3, we iterated over an array using a `for` loop to execute some code on each of its items. Under the hood this implicitly created and then consumed an iterator, but we glossed over how exactly that works until now. -The example in Listing 13-14 separates the creation of the iterator from the +The example in Listing 13-11 separates the creation of the iterator from the use of the iterator in the `for` loop. The iterator is stored in the `v1_iter` variable, and no iteration takes place at that time. When the `for` loop is called using the iterator in `v1_iter`, each element in the iterator is used in one iteration of the loop, which prints out each value. ```rust -{{#rustdoc_include ../listings/ch13-functional-features/listing-13-14/src/main.rs:here}} +{{#rustdoc_include ../listings/ch13-functional-features/listing-13-11/src/main.rs:here}} ``` -Listing 13-14: Using an iterator in a `for` loop +Listing 13-11: Using an iterator in a `for` loop In languages that don’t have iterators provided by their standard libraries, you would likely write this same functionality by starting a variable at index @@ -72,17 +72,17 @@ The `Iterator` trait only requires implementors to define one method: the `next` method, which returns one item of the iterator at a time wrapped in `Some` and, when iteration is over, returns `None`. -We can call the `next` method on iterators directly; Listing 13-15 demonstrates +We can call the `next` method on iterators directly; Listing 13-12 demonstrates what values are returned from repeated calls to `next` on the iterator created from the vector. Filename: src/lib.rs ```rust,noplayground -{{#rustdoc_include ../listings/ch13-functional-features/listing-13-15/src/lib.rs:here}} +{{#rustdoc_include ../listings/ch13-functional-features/listing-13-12/src/lib.rs:here}} ``` -Listing 13-15: Calling the `next` method on an +Listing 13-12: Calling the `next` method on an iterator Note that we needed to make `v1_iter` mutable: calling the `next` method on an @@ -112,16 +112,16 @@ Methods that call `next` are called *consuming adaptors*, because calling them uses up the iterator. One example is the `sum` method, which takes ownership of the iterator and iterates through the items by repeatedly calling `next`, thus consuming the iterator. As it iterates through, it adds each item to a running -total and returns the total when iteration is complete. Listing 13-16 has a +total and returns the total when iteration is complete. Listing 13-13 has a test illustrating a use of the `sum` method: Filename: src/lib.rs ```rust,noplayground -{{#rustdoc_include ../listings/ch13-functional-features/listing-13-16/src/lib.rs:here}} +{{#rustdoc_include ../listings/ch13-functional-features/listing-13-13/src/lib.rs:here}} ``` -Listing 13-16: Calling the `sum` method to get the total +Listing 13-13: Calling the `sum` method to get the total of all items in the iterator We aren’t allowed to use `v1_iter` after the call to `sum` because `sum` takes @@ -135,7 +135,7 @@ multiple calls to iterator adaptors to perform complex actions in a readable way. But because all iterators are lazy, you have to call one of the consuming adaptor methods to get results from calls to iterator adaptors. -Listing 13-17 shows an example of calling the iterator adaptor method `map`, +Listing 13-14 shows an example of calling the iterator adaptor method `map`, which takes a closure to call on each item to produce a new iterator. The closure here creates a new iterator in which each item from the vector has been incremented by 1. However, this code produces a warning: @@ -143,19 +143,19 @@ incremented by 1. However, this code produces a warning: Filename: src/main.rs ```rust,not_desired_behavior -{{#rustdoc_include ../listings/ch13-functional-features/listing-13-17/src/main.rs:here}} +{{#rustdoc_include ../listings/ch13-functional-features/listing-13-14/src/main.rs:here}} ``` -Listing 13-17: Calling the iterator adaptor `map` to +Listing 13-14: Calling the iterator adaptor `map` to create a new iterator The warning we get is this: ```console -{{#include ../listings/ch13-functional-features/listing-13-17/output.txt}} +{{#include ../listings/ch13-functional-features/listing-13-14/output.txt}} ``` -The code in Listing 13-17 doesn’t do anything; the closure we’ve specified +The code in Listing 13-14 doesn’t do anything; the closure we’ve specified never gets called. The warning reminds us why: iterator adaptors are lazy, and we need to consume the iterator here. @@ -163,17 +163,17 @@ To fix this and consume the iterator, we’ll use the `collect` method, which we used in Chapter 12 with `env::args` in Listing 12-1. This method consumes the iterator and collects the resulting values into a collection data type. -In Listing 13-18, we collect the results of iterating over the iterator that’s +In Listing 13-15, we collect the results of iterating over the iterator that’s returned from the call to `map` into a vector. This vector will end up containing each item from the original vector incremented by 1. Filename: src/main.rs ```rust -{{#rustdoc_include ../listings/ch13-functional-features/listing-13-18/src/main.rs:here}} +{{#rustdoc_include ../listings/ch13-functional-features/listing-13-15/src/main.rs:here}} ``` -Listing 13-18: Calling the `map` method to create a new +Listing 13-15: Calling the `map` method to create a new iterator and then calling the `collect` method to consume the new iterator and create a vector @@ -191,17 +191,17 @@ the iterator and returns a Boolean. If the closure returns `true`, the value will be included in the iterator produced by `filter`. If the closure returns `false`, the value won’t be included in the resulting iterator. -In Listing 13-19, we use `filter` with a closure that captures the `shoe_size` +In Listing 13-16, we use `filter` with a closure that captures the `shoe_size` variable from its environment to iterate over a collection of `Shoe` struct instances. It will return only shoes that are the specified size. Filename: src/lib.rs ```rust,noplayground -{{#rustdoc_include ../listings/ch13-functional-features/listing-13-19/src/lib.rs}} +{{#rustdoc_include ../listings/ch13-functional-features/listing-13-16/src/lib.rs}} ``` -Listing 13-19: Using the `filter` method with a closure +Listing 13-16: Using the `filter` method with a closure that captures `shoe_size` The `shoes_in_size` function takes ownership of a vector of shoes and a shoe diff --git a/src/ch13-03-improving-our-io-project.md b/src/ch13-03-improving-our-io-project.md index 104fcb195..2f5d6cdc6 100644 --- a/src/ch13-03-improving-our-io-project.md +++ b/src/ch13-03-improving-our-io-project.md @@ -9,7 +9,7 @@ concise. Let’s look at how iterators can improve our implementation of the In Listing 12-6, we added code that took a slice of `String` values and created an instance of the `Config` struct by indexing into the slice and cloning the -values, allowing the `Config` struct to own those values. In Listing 13-24, +values, allowing the `Config` struct to own those values. In Listing 13-17, we’ve reproduced the implementation of the `Config::new` function as it was in Listing 12-23: @@ -19,7 +19,7 @@ Listing 12-23: {{#rustdoc_include ../listings/ch13-functional-features/listing-12-23-reproduced/src/lib.rs:ch13}} ``` -Listing 13-24: Reproduction of the `Config::new` function +Listing 13-17: Reproduction of the `Config::new` function from Listing 12-23 At the time, we said not to worry about the inefficient `clone` calls because @@ -51,16 +51,16 @@ Open your I/O project’s *src/main.rs* file, which should look like this: ``` We’ll change the start of the `main` function that we had in Listing 12-24 to -the code in Listing 13-25. This won’t compile until we update `Config::new` as +the code in Listing 13-18. This won’t compile until we update `Config::new` as well. Filename: src/main.rs ```rust,ignore,does_not_compile -{{#rustdoc_include ../listings/ch13-functional-features/listing-13-25/src/main.rs:here}} +{{#rustdoc_include ../listings/ch13-functional-features/listing-13-18/src/main.rs:here}} ``` -Listing 13-25: Passing the return value of `env::args` to +Listing 13-18: Passing the return value of `env::args` to `Config::new` The `env::args` function returns an iterator! Rather than collecting the @@ -70,16 +70,16 @@ we’re passing ownership of the iterator returned from `env::args` to Next, we need to update the definition of `Config::new`. In your I/O project’s *src/lib.rs* file, let’s change the signature of `Config::new` to look like -Listing 13-26. This still won’t compile because we need to update the function +Listing 13-19. This still won’t compile because we need to update the function body. Filename: src/lib.rs ```rust,ignore,does_not_compile -{{#rustdoc_include ../listings/ch13-functional-features/listing-13-26/src/lib.rs:here}} +{{#rustdoc_include ../listings/ch13-functional-features/listing-13-19/src/lib.rs:here}} ``` -Listing 13-26: Updating the signature of `Config::new` to +Listing 13-19: Updating the signature of `Config::new` to expect an iterator The standard library documentation for the `env::args` function shows that the @@ -100,16 +100,16 @@ iterating over it, we can add the `mut` keyword into the specification of the #### Using `Iterator` Trait Methods Instead of Indexing Next, we’ll fix the body of `Config::new`. Because `args` implements the -`Iterator` trait, we know we can call the `next` method on it! Listing 13-27 +`Iterator` trait, we know we can call the `next` method on it! Listing 13-20 updates the code from Listing 12-23 to use the `next` method: Filename: src/lib.rs ```rust,noplayground -{{#rustdoc_include ../listings/ch13-functional-features/listing-13-27/src/lib.rs:here}} +{{#rustdoc_include ../listings/ch13-functional-features/listing-13-20/src/lib.rs:here}} ``` -Listing 13-27: Changing the body of `Config::new` to use +Listing 13-20: Changing the body of `Config::new` to use iterator methods Remember that the first value in the return value of `env::args` is the name of @@ -123,7 +123,7 @@ the same thing for the `filename` value. ### Making Code Clearer with Iterator Adaptors We can also take advantage of iterators in the `search` function in our I/O -project, which is reproduced here in Listing 13-28 as it was in Listing 12-19: +project, which is reproduced here in Listing 13-21 as it was in Listing 12-19: Filename: src/lib.rs @@ -131,7 +131,7 @@ project, which is reproduced here in Listing 13-28 as it was in Listing 12-19: {{#rustdoc_include ../listings/ch12-an-io-project/listing-12-19/src/lib.rs:ch13}} ``` -Listing 13-28: The implementation of the `search` +Listing 13-21: The implementation of the `search` function from Listing 12-19 We can write this code in a more concise way using iterator adaptor methods. @@ -139,28 +139,28 @@ Doing so also lets us avoid having a mutable intermediate `results` vector. The functional programming style prefers to minimize the amount of mutable state to make code clearer. Removing the mutable state might enable a future enhancement to make searching happen in parallel, because we wouldn’t have to manage -concurrent access to the `results` vector. Listing 13-29 shows this change: +concurrent access to the `results` vector. Listing 13-22 shows this change: Filename: src/lib.rs ```rust,ignore -{{#rustdoc_include ../listings/ch13-functional-features/listing-13-29/src/lib.rs:here}} +{{#rustdoc_include ../listings/ch13-functional-features/listing-13-22/src/lib.rs:here}} ``` -Listing 13-29: Using iterator adaptor methods in the +Listing 13-22: Using iterator adaptor methods in the implementation of the `search` function Recall that the purpose of the `search` function is to return all lines in `contents` that contain the `query`. Similar to the `filter` example in Listing -13-19, this code uses the `filter` adaptor to keep only the lines that +13-16, this code uses the `filter` adaptor to keep only the lines that `line.contains(query)` returns `true` for. We then collect the matching lines into another vector with `collect`. Much simpler! Feel free to make the same change to use iterator methods in the `search_case_insensitive` function as well. The next logical question is which style you should choose in your own code and -why: the original implementation in Listing 13-28 or the version using -iterators in Listing 13-29. Most Rust programmers prefer to use the iterator +why: the original implementation in Listing 13-21 or the version using +iterators in Listing 13-22. Most Rust programmers prefer to use the iterator style. It’s a bit tougher to get the hang of at first, but once you get a feel for the various iterator adaptors and what they do, iterators can be easier to understand. Instead of fiddling with the various bits of looping and building diff --git a/src/ch19-03-advanced-traits.md b/src/ch19-03-advanced-traits.md index f2756a35b..850d58830 100644 --- a/src/ch19-03-advanced-traits.md +++ b/src/ch19-03-advanced-traits.md @@ -22,10 +22,8 @@ the other features discussed in this chapter. One example of a trait with an associated type is the `Iterator` trait that the standard library provides. The associated type is named `Item` and stands in for the type of the values the type implementing the `Iterator` trait is -iterating over. In [“The `Iterator` Trait and the `next` -Method”][the-iterator-trait-and-the-next-method] section of -Chapter 13, we mentioned that the definition of the `Iterator` trait is as -shown in Listing 19-12. +iterating over. The definition of the `Iterator` trait is as shown in Listing +19-12. ```rust,noplayground {{#rustdoc_include ../listings/ch19-advanced-features/listing-19-12/src/lib.rs}} @@ -43,14 +41,14 @@ 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 -Listing 13-21, we specified that the `Item` type was `u32`: +Let’s examine the difference between the two concepts with an example that +implements the `Iterator` trait on a `Counter` struct. This implementation +specifies the `Item` type is `u32`: Filename: src/lib.rs ```rust,ignore -{{#rustdoc_include ../listings/ch19-advanced-features/listing-13-21-reproduced/src/lib.rs:ch19}} +{{#rustdoc_include ../listings/ch19-advanced-features/no-listing-22-iterator-on-counter/src/lib.rs:ch19}} ``` This syntax seems comparable to that of generics. So why not just define the @@ -459,8 +457,6 @@ at some advanced ways to interact with Rust’s type system. [newtype]: ch19-03-advanced-traits.html#using-the-newtype-pattern-to-implement-external-traits-on-external-types [implementing-a-trait-on-a-type]: ch10-02-traits.html#implementing-a-trait-on-a-type -[the-iterator-trait-and-the-next-method]: -ch13-02-iterators.html#the-iterator-trait-and-the-next-method [traits-defining-shared-behavior]: ch10-02-traits.html#traits-defining-shared-behavior [smart-pointer-deref]: ch15-02-deref.html#treating-smart-pointers-like-regular-references-with-the-deref-trait