diff --git a/ci/dictionary.txt b/ci/dictionary.txt index d8aeaa8a43..c83a6268d0 100644 --- a/ci/dictionary.txt +++ b/ci/dictionary.txt @@ -168,6 +168,7 @@ eprintln Erlang ErrorKind Español +ETAPS eval executables ExitCode @@ -624,6 +625,7 @@ wasi wasn weakt WeatherForecast +webpage WebSocket whitespace wildcard diff --git a/listings/ch11-writing-automated-tests/no-listing-10-result-in-tests/src/lib.rs b/listings/ch11-writing-automated-tests/no-listing-10-result-in-tests/src/lib.rs index 06b1a03e1f..87db05b619 100644 --- a/listings/ch11-writing-automated-tests/no-listing-10-result-in-tests/src/lib.rs +++ b/listings/ch11-writing-automated-tests/no-listing-10-result-in-tests/src/lib.rs @@ -2,11 +2,11 @@ pub fn add(left: u64, right: u64) -> u64 { left + right } +// ANCHOR: here #[cfg(test)] mod tests { use super::*; - // ANCHOR: here #[test] fn it_works() -> Result<(), String> { let result = add(2, 2); @@ -17,5 +17,5 @@ mod tests { Err(String::from("two plus two does not equal four")) } } - // ANCHOR_END: here } +// ANCHOR_END: here diff --git a/listings/ch15-smart-pointers/listing-15-14/output.txt b/listings/ch15-smart-pointers/listing-15-14/output.txt index 1393d44b33..939bc7f458 100644 --- a/listings/ch15-smart-pointers/listing-15-14/output.txt +++ b/listings/ch15-smart-pointers/listing-15-14/output.txt @@ -2,6 +2,6 @@ $ cargo run Compiling drop-example v0.1.0 (file:///projects/drop-example) Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.60s Running `target/debug/drop-example` -CustomSmartPointers created. +CustomSmartPointers created Dropping CustomSmartPointer with data `other stuff`! Dropping CustomSmartPointer with data `my stuff`! diff --git a/listings/ch15-smart-pointers/listing-15-14/src/main.rs b/listings/ch15-smart-pointers/listing-15-14/src/main.rs index 231612ae62..bcf95160f6 100644 --- a/listings/ch15-smart-pointers/listing-15-14/src/main.rs +++ b/listings/ch15-smart-pointers/listing-15-14/src/main.rs @@ -15,5 +15,5 @@ fn main() { let d = CustomSmartPointer { data: String::from("other stuff"), }; - println!("CustomSmartPointers created."); + println!("CustomSmartPointers created"); } diff --git a/listings/ch15-smart-pointers/listing-15-15/src/main.rs b/listings/ch15-smart-pointers/listing-15-15/src/main.rs index ff3b391a91..5fddbac967 100644 --- a/listings/ch15-smart-pointers/listing-15-15/src/main.rs +++ b/listings/ch15-smart-pointers/listing-15-15/src/main.rs @@ -13,8 +13,8 @@ fn main() { let c = CustomSmartPointer { data: String::from("some data"), }; - println!("CustomSmartPointer created."); + println!("CustomSmartPointer created"); c.drop(); - println!("CustomSmartPointer dropped before the end of main."); + println!("CustomSmartPointer dropped before the end of main"); } // ANCHOR_END: here diff --git a/listings/ch15-smart-pointers/listing-15-16/output.txt b/listings/ch15-smart-pointers/listing-15-16/output.txt index f032d84b6b..ae2ed04d08 100644 --- a/listings/ch15-smart-pointers/listing-15-16/output.txt +++ b/listings/ch15-smart-pointers/listing-15-16/output.txt @@ -2,6 +2,6 @@ $ cargo run Compiling drop-example v0.1.0 (file:///projects/drop-example) Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.73s Running `target/debug/drop-example` -CustomSmartPointer created. +CustomSmartPointer created Dropping CustomSmartPointer with data `some data`! -CustomSmartPointer dropped before the end of main. +CustomSmartPointer dropped before the end of main diff --git a/listings/ch15-smart-pointers/listing-15-16/src/main.rs b/listings/ch15-smart-pointers/listing-15-16/src/main.rs index f11715c45e..af62879976 100644 --- a/listings/ch15-smart-pointers/listing-15-16/src/main.rs +++ b/listings/ch15-smart-pointers/listing-15-16/src/main.rs @@ -13,8 +13,8 @@ fn main() { let c = CustomSmartPointer { data: String::from("some data"), }; - println!("CustomSmartPointer created."); + println!("CustomSmartPointer created"); drop(c); - println!("CustomSmartPointer dropped before the end of main."); + println!("CustomSmartPointer dropped before the end of main"); } // ANCHOR_END: here diff --git a/listings/ch15-smart-pointers/listing-15-25/src/main.rs b/listings/ch15-smart-pointers/listing-15-25/src/main.rs index f36c7fd06d..6051014de7 100644 --- a/listings/ch15-smart-pointers/listing-15-25/src/main.rs +++ b/listings/ch15-smart-pointers/listing-15-25/src/main.rs @@ -1,3 +1,4 @@ +// ANCHOR: here use crate::List::{Cons, Nil}; use std::cell::RefCell; use std::rc::Rc; @@ -16,5 +17,6 @@ impl List { } } } +// ANCHOR_END: here fn main() {} diff --git a/nostarch/appendix.md b/nostarch/appendix.md index 7a2a964f3a..53b7739821 100644 --- a/nostarch/appendix.md +++ b/nostarch/appendix.md @@ -13,10 +13,10 @@ Rust journey. ## Appendix A: Keywords -The following list contains keywords that are reserved for current or future +The following lists contain keywords that are reserved for current or future use by the Rust language. As such, they cannot be used as identifiers (except -as raw identifiers as we’ll discuss in the “Raw -Identifiers” section). Identifiers are names +as raw identifiers, as we discuss in the “Raw +Identifiers” section). *Identifiers* are names of functions, variables, parameters, struct fields, modules, crates, constants, macros, static values, attributes, types, traits, or lifetimes. @@ -25,54 +25,55 @@ macros, static values, attributes, types, traits, or lifetimes. The following is a list of keywords currently in use, with their functionality described. -* `as` - perform primitive casting, disambiguate the specific trait containing - an item, or rename items in `use` statements -* `async` - return a `Future` instead of blocking the current thread -* `await` - suspend execution until the result of a `Future` is ready -* `break` - exit a loop immediately -* `const` - define constant items or constant raw pointers -* `continue` - continue to the next loop iteration -* `crate` - in a module path, refers to the crate root -* `dyn` - dynamic dispatch to a trait object -* `else` - fallback for `if` and `if let` control flow constructs -* `enum` - define an enumeration -* `extern` - link an external function or variable -* `false` - Boolean false literal -* `fn` - define a function or the function pointer type -* `for` - loop over items from an iterator, implement a trait, or specify a - higher-ranked lifetime -* `if` - branch based on the result of a conditional expression -* `impl` - implement inherent or trait functionality -* `in` - part of `for` loop syntax -* `let` - bind a variable -* `loop` - loop unconditionally -* `match` - match a value to patterns -* `mod` - define a module -* `move` - make a closure take ownership of all its captures -* `mut` - denote mutability in references, raw pointers, or pattern bindings -* `pub` - denote public visibility in struct fields, `impl` blocks, or modules -* `ref` - bind by reference -* `return` - return from function -* `Self` - a type alias for the type we are defining or implementing -* `self` - method subject or current module -* `static` - global variable or lifetime lasting the entire program execution -* `struct` - define a structure -* `super` - parent module of the current module -* `trait` - define a trait -* `true` - Boolean true literal -* `type` - define a type alias or associated type -* `union` - define a union; is only a keyword when used - in a union declaration -* `unsafe` - denote unsafe code, functions, traits, or implementations -* `use` - bring symbols into scope; specify precise captures for generic and - lifetime bounds -* `where` - denote clauses that constrain a type -* `while` - loop conditionally based on the result of an expression +* **`as`**: Perform primitive casting, disambiguate the specific trait + containing an item, or rename items in `use` statements. +* **`async`**: Return a `Future` instead of blocking the current thread. +* **`await`**: Suspend execution until the result of a `Future` is ready. +* **`break`**: Exit a loop immediately. +* **`const`**: Define constant items or constant raw pointers. +* **`continue`**: Continue to the next loop iteration. +* **`crate`**: In a module path, refers to the crate root. +* **`dyn`**: Dynamic dispatch to a trait object. +* **`else`**: Fallback for `if` and `if let` control flow constructs. +* **`enum`**: Define an enumeration. +* **`extern`**: Link an external function or variable. +* **`false`**: Boolean false literal. +* **`fn`**: Define a function or the function pointer type. +* **`for`**: Loop over items from an iterator, implement a trait, or specify a + higher ranked lifetime. +* **`if`**: Branch based on the result of a conditional expression. +* **`impl`**: Implement inherent or trait functionality. +* **`in`**: Part of `for` loop syntax. +* **`let`**: Bind a variable. +* **`loop`**: Loop unconditionally. +* **`match`**: Match a value to patterns. +* **`mod`**: Define a module. +* **`move`**: Make a closure take ownership of all its captures. +* **`mut`**: Denote mutability in references, raw pointers, or pattern bindings. +* **`pub`**: Denote public visibility in struct fields, `impl` blocks, or + modules. +* **`ref`**: Bind by reference. +* **`return`**: Return from function. +* **`Self`**: A type alias for the type we are defining or implementing. +* **`self`**: Method subject or current module. +* **`static`**: Global variable or lifetime lasting the entire program + execution. +* **`struct`**: Define a structure. +* **`super`**: Parent module of the current module. +* **`trait`**: Define a trait. +* **`true`**: Boolean true literal. +* **`type`**: Define a type alias or associated type. +* **`union`**: Define a union; is a keyword only when + used in a union declaration. +* **`unsafe`**: Denote unsafe code, functions, traits, or implementations. +* **`use`**: Bring symbols into scope. +* **`where`**: Denote clauses that constrain a type. +* **`while`**: Loop conditionally based on the result of an expression. ### Keywords Reserved for Future Use The following keywords do not yet have any functionality but are reserved by -Rust for potential future use. +Rust for potential future use: * `abstract` * `become` @@ -140,10 +141,10 @@ identifier names, as well as lets us integrate with programs written in a language where these words aren’t keywords. In addition, raw identifiers allow you to use libraries written in a different Rust edition than your crate uses. For example, `try` isn’t a keyword in the 2015 edition but is in the 2018, 2021, -and 2024 editions. If you depend on a library that’s written using the 2015 +and 2024 editions. If you depend on a library that is written using the 2015 edition and has a `try` function, you’ll need to use the raw identifier syntax, -`r#try` in this case, to call that function from your 2018 edition code. See -Appendix E for more information on editions. +`r#try` in this case, to call that function from your code on later editions. +See Appendix E for more information on editions. ## Appendix B: Operators and Symbols @@ -186,7 +187,7 @@ Table B-1: Operators |`->`|`fn(...) -> type`, \|...\| -> type|Function and closure return type|| |`.`|`expr.ident`|Field access|| |`.`|`expr.ident(expr, ...)`|Method call|| -|`.`|`expr.0`, `expr.1`, etc.|Tuple indexing|| +|`.`|`expr.0`, `expr.1`, and so on|Tuple indexing|| |`..`|`..`, `expr..`, `..expr`, `expr..expr`|Right-exclusive range literal|`PartialOrd`| |`..=`|`..=expr`, `expr..=expr`|Right-inclusive range literal|`PartialOrd`| |`..`|`..expr`|Struct literal update syntax|| @@ -221,26 +222,26 @@ Table B-1: Operators ### Non-operator Symbols -The following list contains all symbols that don’t function as operators; that +The following tables contain all symbols that don’t function as operators; that is, they don’t behave like a function or method call. Table B-2 shows symbols that appear on their own and are valid in a variety of locations. -Table B-2: Stand-Alone Syntax +Table B-2: Stand-alone Syntax |Symbol|Explanation| |------|-----------| |`'ident`|Named lifetime or loop label| -|Digits immediately followed by `u8`, `i32`, `f64`, `usize`, and so on|Numeric literal of specific type| +|Digits immediately followed by `u8`, `i32`, `f64`, `usize`, and so on|Numeric literal of specific type| |`"..."`|String literal| -|`r"..."`, `r#"..."#`, `r##"..."##`, etc.|Raw string literal, escape characters not processed| +|`r"..."`, `r#"..."#`, `r##"..."##`, and so on|Raw string literal; escape characters not processed| |`b"..."`|Byte string literal; constructs an array of bytes instead of a string| -|`br"..."`, `br#"..."#`, `br##"..."##`, etc.|Raw byte string literal, combination of raw and byte string literal| +|`br"..."`, `br#"..."#`, `br##"..."##`, and so on|Raw byte string literal; combination of raw and byte string literal| |`'...'`|Character literal| |`b'...'`|ASCII byte literal| |\|...\| expr|Closure| -|`!`|Always empty bottom type for diverging functions| +|`!`|Always-empty bottom type for diverging functions| |`_`|“Ignored” pattern binding; also used to make integer literals readable| Table B-3 shows symbols that appear in the context of a path through the module @@ -251,11 +252,11 @@ Table B-3: Path-Related Syntax |Symbol|Explanation| |------|-----------| |`ident::ident`|Namespace path| -|`::path`|Path relative to the extern prelude, where all other crates are rooted (i.e., an explicitly absolute path including crate name)| -|`self::path`|Path relative to the current module (i.e., an explicitly relative path).| +|`::path`|Path relative to the crate root (that is, an explicitly absolute path)| +|`self::path`|Path relative to the current module (that is, an explicitly relative path)| |`super::path`|Path relative to the parent of the current module| |`type::ident`, `::ident`|Associated constants, functions, and types| -|`::...`|Associated item for a type that cannot be directly named (e.g., `<&T>::...`, `<[T]>::...`, etc.)| +|`::...`|Associated item for a type that cannot be directly named (for example, `<&T>::...`, `<[T]>::...`, and so on)| |`trait::method(...)`|Disambiguating a method call by naming the trait that defines it| |`type::method(...)`|Disambiguating a method call by naming the type for which it’s defined| |`::method(...)`|Disambiguating a method call by naming the trait and type| @@ -267,14 +268,14 @@ Table B-4: Generics |Symbol|Explanation| |------|-----------| -|`path<...>`|Specifies parameters to generic type in a type (e.g., `Vec`)| -|`path::<...>`, `method::<...>`|Specifies parameters to generic type, function, or method in an expression; often referred to as turbofish (e.g., `"42".parse::()`)| +|`path<...>`|Specifies parameters to a generic type in a type (for example, `Vec`)| +|`path::<...>`, `method::<...>`|Specifies parameters to a generic type, function, or method in an expression; often referred to as *turbofish* (for example, `"42".parse::()`)| |`fn ident<...> ...`|Define generic function| |`struct ident<...> ...`|Define generic structure| |`enum ident<...> ...`|Define generic enumeration| |`impl<...> ...`|Define generic implementation| -|`for<...> type`|Higher-ranked lifetime bounds| -|`type`|A generic type where one or more associated types have specific assignments (e.g., `Iterator`)| +|`for<...> type`|Higher ranked lifetime bounds| +|`type`|A generic type where one or more associated types have specific assignments (for example, `Iterator`)| Table B-5 shows symbols that appear in the context of constraining generic type parameters with trait bounds. @@ -331,7 +332,7 @@ Table B-8: Parentheses |`(type, ...)`|Tuple type| |`expr(expr, ...)`|Function call expression; also used to initialize tuple `struct`s and tuple `enum` variants| -Table B-9 shows the contexts in which curly braces are used. +Table B-9 shows the contexts in which curly brackets are used. Table B-9: Curly Brackets @@ -349,7 +350,7 @@ Table B-10: Square Brackets |`[...]`|Array literal| |`[expr; len]`|Array literal containing `len` copies of `expr`| |`[type; len]`|Array type containing `len` instances of `type`| -|`expr[expr]`|Collection indexing. Overloadable (`Index`, `IndexMut`)| +|`expr[expr]`|Collection indexing; overloadable (`Index`, `IndexMut`)| |`expr[..]`, `expr[a..]`, `expr[..b]`, `expr[a..b]`|Collection indexing pretending to be collection slicing, using `Range`, `RangeFrom`, `RangeTo`, or `RangeFull` as the “index”| ## Appendix C: Derivable Traits @@ -370,9 +371,9 @@ library that you can use with `derive`. Each section covers: If you want different behavior from that provided by the `derive` attribute, consult the standard library documentation -for each trait for details of how to manually implement them. +for each trait for details on how to manually implement them. -These traits listed here are the only ones defined by the standard library that +The traits listed here are the only ones defined by the standard library that can be implemented on your types using `derive`. Other traits defined in the standard library don’t have sensible default behavior, so it’s up to you to implement them in the way that makes sense for what you’re trying to accomplish. @@ -385,10 +386,10 @@ would be most relevant to them? The Rust compiler doesn’t have this insight, s it can’t provide appropriate default behavior for you. The list of derivable traits provided in this appendix is not comprehensive: -libraries can implement `derive` for their own traits, making the list of -traits you can use `derive` with truly open-ended. Implementing `derive` -involves using a procedural macro, which is covered in the -“Macros” section of Chapter 20. +Libraries can implement `derive` for their own traits, making the list of +traits you can use `derive` with truly open ended. Implementing `derive` +involves using a procedural macro, which is covered in the “Custom `derive` +Macros” section in Chapter 20. ### Debug for Programmer Output @@ -399,9 +400,10 @@ The `Debug` trait allows you to print instances of a type for debugging purposes, so you and other programmers using your type can inspect an instance at a particular point in a program’s execution. -The `Debug` trait is required, for example, in using the `assert_eq!` macro. -This macro prints the values of instances given as arguments if the equality -assertion fails so programmers can see why the two instances weren’t equal. +The `Debug` trait is required, for example, in the use of the `assert_eq!` +macro. This macro prints the values of instances given as arguments if the +equality assertion fails so that programmers can see why the two instances +weren’t equal. ### PartialEq and Eq for Equality Comparisons @@ -410,7 +412,7 @@ equality and enables use of the `==` and `!=` operators. Deriving `PartialEq` implements the `eq` method. When `PartialEq` is derived on structs, two instances are equal only if *all* fields are equal, and the -instances are not equal if any fields are not equal. When derived on enums, +instances are not equal if *any* fields are not equal. When derived on enums, each variant is equal to itself and not equal to the other variants. The `PartialEq` trait is required, for example, with the use of the @@ -420,12 +422,12 @@ for equality. The `Eq` trait has no methods. Its purpose is to signal that for every value of the annotated type, the value is equal to itself. The `Eq` trait can only be applied to types that also implement `PartialEq`, although not all types that -implement `PartialEq` can implement `Eq`. One example of this is floating point -number types: the implementation of floating point numbers states that two +implement `PartialEq` can implement `Eq`. One example of this is floating-point +number types: The implementation of floating-point numbers states that two instances of the not-a-number (`NaN`) value are not equal to each other. -An example of when `Eq` is required is for keys in a `HashMap` so the -`HashMap` can tell whether two keys are the same. +An example of when `Eq` is required is for keys in a `HashMap` so that +the `HashMap` can tell whether two keys are the same. ### PartialOrd and Ord for Ordering Comparisons @@ -438,8 +440,8 @@ Deriving `PartialOrd` implements the `partial_cmp` method, which returns an `Option` that will be `None` when the values given don’t produce an ordering. An example of a value that doesn’t produce an ordering, even though most values of that type can be compared, is the `NaN` floating point value. -Calling `partial_cmp` with any floating point number and the `NaN` floating -point value will return `None`. +Calling `partial_cmp` with any floating-point number and the `NaN` +floating-point value will return `None`. When derived on structs, `PartialOrd` compares two instances by comparing the value in each field in the order in which the fields appear in the struct @@ -465,9 +467,9 @@ a data structure that stores data based on the sort order of the values. The `Clone` trait allows you to explicitly create a deep copy of a value, and the duplication process might involve running arbitrary code and copying heap -data. See Variables and Data Interacting with -Clone” in Chapter 4 -for more information on `Clone`. +data. See the “Variables and Data Interacting with +Clone” section in +Chapter 4 for more information on `Clone`. Deriving `Clone` implements the `clone` method, which when implemented for the whole type, calls `clone` on each of the parts of the type. This means all the @@ -479,9 +481,9 @@ returned from `to_vec` will need to own its instances, so `to_vec` calls `clone` on each item. Thus, the type stored in the slice must implement `Clone`. The `Copy` trait allows you to duplicate a value by only copying bits stored on -the stack; no arbitrary code is necessary. See “Stack-Only Data: -Copy” in Chapter 4 for more information on -`Copy`. +the stack; no arbitrary code is necessary. See the “Stack-Only Data: +Copy” section in Chapter 4 for more +information on `Copy`. The `Copy` trait doesn’t define any methods to prevent programmers from overloading those methods and violating the assumption that no arbitrary code @@ -489,7 +491,7 @@ is being run. That way, all programmers can assume that copying a value will be very fast. You can derive `Copy` on any type whose parts all implement `Copy`. A type that -implements `Copy` must also implement `Clone`, because a type that implements +implements `Copy` must also implement `Clone` because a type that implements `Copy` has a trivial implementation of `Clone` that performs the same task as `Copy`. @@ -520,10 +522,10 @@ meaning all fields or values in the type must also implement `Default` to derive `Default`. The `Default::default` function is commonly used in combination with the struct -update syntax discussed in “Creating Instances From Other Instances With Struct -Update -Syntax” in Chapter 5. You can customize a few fields of a struct and then set -and use a default value for the rest of the fields by using +update syntax discussed in the “Creating Instances from Other Instances with +Struct Update +Syntax” section in Chapter 5. You can customize a few fields of a struct and +then set and use a default value for the rest of the fields by using `..Default::default()`. The `Default` trait is required when you use the method `unwrap_or_default` on @@ -531,7 +533,7 @@ The `Default` trait is required when you use the method `unwrap_or_default` on `unwrap_or_default` will return the result of `Default::default` for the type `T` stored in the `Option`. -## Appendix D - Useful Development Tools +## Appendix D: Useful Development Tools In this appendix, we talk about some useful development tools that the Rust project provides. We’ll look at automatic formatting, quick ways to apply @@ -541,11 +543,11 @@ warning fixes, a linter, and integrating with IDEs. The `rustfmt` tool reformats your code according to the community code style. Many collaborative projects use `rustfmt` to prevent arguments about which -style to use when writing Rust: everyone formats their code using the tool. +style to use when writing Rust: Everyone formats their code using the tool. Rust installations include `rustfmt` by default, so you should already have the programs `rustfmt` and `cargo-fmt` on your system. These two commands are -analogous to `rustc` and `cargo` in that `rustfmt` allows finer-grained control +analogous to `rustc` and `cargo` in that `rustfmt` allows finer grained control and `cargo-fmt` understands conventions of a project that uses Cargo. To format any Cargo project, enter the following: @@ -559,10 +561,10 @@ on `rustfmt`, see its documentation at *https://github.com/rust-lang/rustfmt*. ### Fix Your Code with rustfix -The `rustfix` tool is included with Rust installations and can automatically fix -compiler warnings that have a clear way to correct the problem that’s likely -what you want. You’ve probably seen compiler warnings before. For example, -consider this code: +The `rustfix` tool is included with Rust installations and can automatically +fix compiler warnings that have a clear way to correct the problem that’s +likely what you want. You’ve probably seen compiler warnings before. For +example, consider this code: Filename: src/main.rs @@ -615,13 +617,13 @@ fn main() { The variable `x` is now immutable, and the warning no longer appears. You can also use the `cargo fix` command to transition your code between -different Rust editions. Editions are covered in Appendix E at *appendix-05-editions.md*. +different Rust editions. Editions are covered in Appendix E. ### More Lints with Clippy -The Clippy tool is a collection of lints to analyze your code so you can catch -common mistakes and improve your Rust code. Clippy is included with standard -Rust installations. +The Clippy tool is a collection of lints to analyze your code so that you can +catch common mistakes and improve your Rust code. Clippy is included with +standard Rust installations. To run Clippy’s lints on any Cargo project, enter the following: @@ -691,7 +693,7 @@ for installation instructions, then install the language server support in your particular IDE. Your IDE will gain capabilities such as autocompletion, jump to definition, and inline errors. -## Appendix E - Editions +## Appendix E: Editions In Chapter 1, you saw that `cargo new` adds a bit of metadata to your *Cargo.toml* file about an edition. This appendix talks about what that means! @@ -703,7 +705,7 @@ while, all of these tiny changes add up. But from release to release, it can be difficult to look back and say, “Wow, between Rust 1.10 and Rust 1.31, Rust has changed a lot!” -Every two or three years, the Rust team produces a new Rust *edition*. Each +Every three years or so, the Rust team produces a new Rust *edition*. Each edition brings together the features that have landed into a clear package with fully updated documentation and tooling. New editions ship as part of the usual six-week release process. @@ -739,15 +741,15 @@ Rust 2018, your project will compile and be able to use that dependency. The opposite situation, where your project uses Rust 2018 and a dependency uses Rust 2015, works as well. -To be clear: most features will be available on all editions. Developers using +To be clear: Most features will be available on all editions. Developers using any Rust edition will continue to see improvements as new stable releases are made. However, in some cases, mainly when new keywords are added, some new features might only be available in later editions. You will need to switch editions if you want to take advantage of such features. -For more details, the *Edition Guide* at *https://doc.rust-lang.org/stable/edition-guide/* is a complete book -about editions that enumerates the differences between editions and explains -how to automatically upgrade your code to a new edition via `cargo fix`. +For more details, see *The Rust Edition Guide* at *https://doc.rust-lang.org/stable/edition-guide*. This is a +complete book that enumerates the differences between editions and explains how +to automatically upgrade your code to a new edition via `cargo fix`. ## Appendix F: Translations of the Book diff --git a/nostarch/appendix_a.md b/nostarch/appendix_a.md index 26791b66ec..92b3c83393 100644 --- a/nostarch/appendix_a.md +++ b/nostarch/appendix_a.md @@ -10,62 +10,62 @@ directory, so all fixes need to be made in `/src/`. The following lists contain keywords that are reserved for current or future use by the Rust language. As such, they cannot be used as identifiers (except -as raw identifiers, as we’ll discuss in “Raw Identifiers” on page XX). -*Identifiers* are names of functions, variables, parameters, struct fields, -modules, crates, constants, macros, static values, attributes, types, traits, -or lifetimes. +as raw identifiers, as we discuss in the “Raw +Identifiers” section). *Identifiers* are names +of functions, variables, parameters, struct fields, modules, crates, constants, +macros, static values, attributes, types, traits, or lifetimes. -## Keywords Currently in Use +### Keywords Currently in Use The following is a list of keywords currently in use, with their functionality described. -* **`as` **: perform primitive casting, disambiguate the specific trait -containing an item, or rename items in `use` statements -* **`async` **: return a `Future` instead of blocking the current thread -* **`await` **: suspend execution until the result of a `Future` is ready -* **`break` **: exit a loop immediately -* **`const` **: define constant items or constant raw pointers -* **`continue` **: continue to the next loop iteration -* **`crate` **: in a module path, refers to the crate root -* **`dyn` **: dynamic dispatch to a trait object -* **`else` **: fallback for `if` and `if let` control flow constructs -* **`enum` **: define an enumeration -* **`extern` **: link an external function or variable -* **`false` **: Boolean false literal -* **`fn` **: define a function or the function pointer type -* **`for` **: loop over items from an iterator, implement a trait, or specify a -higher-ranked lifetime -* **`if` **: branch based on the result of a conditional expression -* **`impl` **: implement inherent or trait functionality -* **`in` **: part of `for` loop syntax -* **`let` **: bind a variable -* **`loop` **: loop unconditionally -* **`match` **: match a value to patterns -* **`mod` **: define a module -* **`move` **: make a closure take ownership of all its captures -* **`mut` **: denote mutability in references, raw pointers, or pattern bindings -* **`pub` **: denote public visibility in struct fields, `impl` blocks, or -modules -* **`ref` **: bind by reference -* **`return` **: return from function -* **`Self` **: a type alias for the type we are defining or implementing -* **`self` **: method subject or current module -* **`static` **: global variable or lifetime lasting the entire program -execution -* **`struct` **: define a structure -* **`super` **: parent module of the current module -* **`trait` **: define a trait -* **`true` **: Boolean true literal -* **`type` **: define a type alias or associated type -* **`union` **: define a union; is a keyword only when used in a union -declaration -* **`unsafe` **: denote unsafe code, functions, traits, or implementations -* **`use` **: bring symbols into scope -* **`where` **: denote clauses that constrain a type -* **`while` **: loop conditionally based on the result of an expression - -## Keywords Reserved for Future Use +* **`as`**: Perform primitive casting, disambiguate the specific trait + containing an item, or rename items in `use` statements. +* **`async`**: Return a `Future` instead of blocking the current thread. +* **`await`**: Suspend execution until the result of a `Future` is ready. +* **`break`**: Exit a loop immediately. +* **`const`**: Define constant items or constant raw pointers. +* **`continue`**: Continue to the next loop iteration. +* **`crate`**: In a module path, refers to the crate root. +* **`dyn`**: Dynamic dispatch to a trait object. +* **`else`**: Fallback for `if` and `if let` control flow constructs. +* **`enum`**: Define an enumeration. +* **`extern`**: Link an external function or variable. +* **`false`**: Boolean false literal. +* **`fn`**: Define a function or the function pointer type. +* **`for`**: Loop over items from an iterator, implement a trait, or specify a + higher ranked lifetime. +* **`if`**: Branch based on the result of a conditional expression. +* **`impl`**: Implement inherent or trait functionality. +* **`in`**: Part of `for` loop syntax. +* **`let`**: Bind a variable. +* **`loop`**: Loop unconditionally. +* **`match`**: Match a value to patterns. +* **`mod`**: Define a module. +* **`move`**: Make a closure take ownership of all its captures. +* **`mut`**: Denote mutability in references, raw pointers, or pattern bindings. +* **`pub`**: Denote public visibility in struct fields, `impl` blocks, or + modules. +* **`ref`**: Bind by reference. +* **`return`**: Return from function. +* **`Self`**: A type alias for the type we are defining or implementing. +* **`self`**: Method subject or current module. +* **`static`**: Global variable or lifetime lasting the entire program + execution. +* **`struct`**: Define a structure. +* **`super`**: Parent module of the current module. +* **`trait`**: Define a trait. +* **`true`**: Boolean true literal. +* **`type`**: Define a type alias or associated type. +* **`union`**: Define a union; is a keyword only when + used in a union declaration. +* **`unsafe`**: Denote unsafe code, functions, traits, or implementations. +* **`use`**: Bring symbols into scope. +* **`where`**: Denote clauses that constrain a type. +* **`while`**: Loop conditionally based on the result of an expression. + +### Keywords Reserved for Future Use The following keywords do not yet have any functionality but are reserved by Rust for potential future use: @@ -75,6 +75,7 @@ Rust for potential future use: * `box` * `do` * `final` +* `gen` * `macro` * `override` * `priv` @@ -84,7 +85,7 @@ Rust for potential future use: * `virtual` * `yield` -## Raw Identifiers +### Raw Identifiers *Raw identifiers* are the syntax that lets you use keywords where they wouldn’t normally be allowed. You use a raw identifier by prefixing a keyword with `r#`. diff --git a/nostarch/appendix_b.md b/nostarch/appendix_b.md index dceca86623..4d894dd51e 100644 --- a/nostarch/appendix_b.md +++ b/nostarch/appendix_b.md @@ -49,7 +49,7 @@ type | | | `->` | `fn(...) -> type`, `|...| -> type` | Function and closure return type | | | `.` | `expr.ident` | Field access | | | `.` | `expr.ident(expr, ...)` | Method call | | -| `.` | `expr.0`, `expr.1`, etc. | Tuple indexing | | +| `.` | `expr.0`, `expr.1`, and so on | Tuple indexing | | | `..` | `..`, `expr..`, `..expr`, `expr..expr` | Right-exclusive range literal | `PartialOrd` | | `..=` | `..=expr`, `expr..=expr` | Right-inclusive range literal | @@ -61,7 +61,7 @@ binding | | inclusive range pattern | | | `/` | `expr / expr` | Arithmetic division | `Div` | | `/=` | `var /= expr` | Arithmetic division and assignment | `DivAssign` | -| `: | `pat: type`, `ident: type` | Constraints | | +| `:` | `pat: type`, `ident: type` | Constraints | | | `:` | `ident: expr` | Struct field initializer | | | `:` | `'a: loop {...}` | Loop label | | | `;` | `expr;` | Statement and item terminator | | @@ -94,12 +94,12 @@ is, they don’t behave like a function or method call. Table B-2 shows symbols that appear on their own and are valid in a variety of locations. -Table B-2: Stand-Alone Syntax +Table B-2: Stand-alone Syntax | Symbol | Explanation | |---|---| | `'ident` | Named lifetime or loop label | -| Digits immediately followed by `u8`, `i32`, `f64`, `usize`, and so on | +| Digits immediately followed by `u8`, `i32`, `f64`, `usize`, and so on | Numeric literal of specific type | | `"..."` | String literal | | `r"..."`, `r#"..."#`, `r##"..."##`, and so on | Raw string literal; escape @@ -147,14 +147,14 @@ Table B-4: Generics |---|---| | `path<...>` | Specifies parameters to a generic type in a type (for example, `Vec`) | -| `path::<...>, method::<...>` | Specifies parameters to a generic type, -function, or method in an expression; often referred to as turbofish (for +| `path::<...>`, `method::<...>` | Specifies parameters to a generic type, +function, or method in an expression; often referred to as *turbofish* (for example, `"42".parse::()`) | | `fn ident<...> ...` | Define generic function | | `struct ident<...> ...` | Define generic structure | | `enum ident<...> ...` | Define generic enumeration | | `impl<...> ...` | Define generic implementation | -| `for<...> type` | Higher-ranked lifetime bounds | +| `for<...> type` | Higher ranked lifetime bounds | | `type` | A generic type where one or more associated types have specific assignments (for example, `Iterator`) | diff --git a/nostarch/appendix_c.md b/nostarch/appendix_c.md index 53131eb5fa..4023b12fa7 100644 --- a/nostarch/appendix_c.md +++ b/nostarch/appendix_c.md @@ -39,9 +39,10 @@ would be most relevant to them? The Rust compiler doesn’t have this insight, s it can’t provide appropriate default behavior for you. The list of derivable traits provided in this appendix is not comprehensive: -libraries can implement `derive` for their own traits, making the list of +Libraries can implement `derive` for their own traits, making the list of traits you can use `derive` with truly open ended. Implementing `derive` -involves using a procedural macro, which is covered in “Macros” on page XX. +involves using a procedural macro, which is covered in the “Custom `derive` +Macros” section in Chapter 20. ## Debug for Programmer Output @@ -54,8 +55,8 @@ at a particular point in a program’s execution. The `Debug` trait is required, for example, in the use of the `assert_eq!` macro. This macro prints the values of instances given as arguments if the -equality assertion fails so programmers can see why the two instances weren’t -equal. +equality assertion fails so that programmers can see why the two instances +weren’t equal. ## PartialEq and Eq for Equality Comparisons @@ -64,7 +65,7 @@ equality and enables use of the `==` and `!=` operators. Deriving `PartialEq` implements the `eq` method. When `PartialEq` is derived on structs, two instances are equal only if *all* fields are equal, and the -instances are not equal if any fields are not equal. When derived on enums, +instances are not equal if *any* fields are not equal. When derived on enums, each variant is equal to itself and not equal to the other variants. The `PartialEq` trait is required, for example, with the use of the @@ -75,7 +76,7 @@ The `Eq` trait has no methods. Its purpose is to signal that for every value of the annotated type, the value is equal to itself. The `Eq` trait can only be applied to types that also implement `PartialEq`, although not all types that implement `PartialEq` can implement `Eq`. One example of this is floating-point -number types: the implementation of floating-point numbers states that two +number types: The implementation of floating-point numbers states that two instances of the not-a-number (`NaN`) value are not equal to each other. An example of when `Eq` is required is for keys in a `HashMap` so that @@ -91,8 +92,8 @@ that also implement `PartialEq`. Deriving `PartialOrd` implements the `partial_cmp` method, which returns an `Option` that will be `None` when the values given don’t produce an ordering. An example of a value that doesn’t produce an ordering, even though -most values of that type can be compared, is the not-a-number (`NaN`) floating -point value. Calling `partial_cmp` with any floating-point number and the `NaN` +most values of that type can be compared, is the `NaN` floating point value. +Calling `partial_cmp` with any floating-point number and the `NaN` floating-point value will return `None`. When derived on structs, `PartialOrd` compares two instances by comparing the @@ -119,8 +120,8 @@ a data structure that stores data based on the sort order of the values. The `Clone` trait allows you to explicitly create a deep copy of a value, and the duplication process might involve running arbitrary code and copying heap -data. See “Variables and Data Interacting with Clone” on page XX for more -information on `Clone`. +data. See the “Variables and Data Interacting with Clone” section in Chapter 4 +for more information on `Clone`. Deriving `Clone` implements the `clone` method, which when implemented for the whole type, calls `clone` on each of the parts of the type. This means all the @@ -129,11 +130,11 @@ fields or values in the type must also implement `Clone` to derive `Clone`. An example of when `Clone` is required is when calling the `to_vec` method on a slice. The slice doesn’t own the type instances it contains, but the vector returned from `to_vec` will need to own its instances, so `to_vec` calls -`clone` on each item. Thus the type stored in the slice must implement `Clone`. +`clone` on each item. Thus, the type stored in the slice must implement `Clone`. The `Copy` trait allows you to duplicate a value by only copying bits stored on -the stack; no arbitrary code is necessary. See “Stack-Only Data: Copy” on page -XX for more information on `Copy`. +the stack; no arbitrary code is necessary. See the “Stack-Only Data: Copy” +section in Chapter 4 for more information on `Copy`. The `Copy` trait doesn’t define any methods to prevent programmers from overloading those methods and violating the assumption that no arbitrary code @@ -172,9 +173,9 @@ meaning all fields or values in the type must also implement `Default` to derive `Default`. The `Default::default` function is commonly used in combination with the struct -update syntax discussed in “Creating Instances from Other Instances with Struct -Update Syntax” on page XX. You can customize a few fields of a struct and then -set and use a default value for the rest of the fields by using +update syntax discussed in the “Creating Instances with Struct Update Syntax” +section in Chapter 5. You can customize a few fields of a struct and then set +and use a default value for the rest of the fields by using `..Default::default()`. The `Default` trait is required when you use the method `unwrap_or_default` on diff --git a/nostarch/appendix_d.md b/nostarch/appendix_d.md index 18e6094cb8..48a5fa33d0 100644 --- a/nostarch/appendix_d.md +++ b/nostarch/appendix_d.md @@ -16,11 +16,11 @@ warning fixes, a linter, and integrating with IDEs. The `rustfmt` tool reformats your code according to the community code style. Many collaborative projects use `rustfmt` to prevent arguments about which -style to use when writing Rust: everyone formats their code using the tool. +style to use when writing Rust: Everyone formats their code using the tool. Rust installations include `rustfmt` by default, so you should already have the programs `rustfmt` and `cargo-fmt` on your system. These two commands are -analogous to `rustc` and `cargo` in that `rustfmt` allows finer-grained control +analogous to `rustc` and `cargo` in that `rustfmt` allows finer grained control and `cargo-fmt` understands conventions of a project that uses Cargo. To format any Cargo project, enter the following: @@ -95,9 +95,9 @@ different Rust editions. Editions are covered in Appendix E. ## More Lints with Clippy -The Clippy tool is a collection of lints to analyze your code so you can catch -common mistakes and improve your Rust code. Clippy is included with standard -Rust installations. +The Clippy tool is a collection of lints to analyze your code so that you can +catch common mistakes and improve your Rust code. Clippy is included with +standard Rust installations. To run Clippy’s lints on any Cargo project, enter the following: diff --git a/nostarch/appendix_e.md b/nostarch/appendix_e.md index ddb12b782b..f7244328cf 100644 --- a/nostarch/appendix_e.md +++ b/nostarch/appendix_e.md @@ -18,7 +18,7 @@ while, all of these tiny changes add up. But from release to release, it can be difficult to look back and say, “Wow, between Rust 1.10 and Rust 1.31, Rust has changed a lot!” -Every two or three years, the Rust team produces a new Rust *edition*. Each +Every three years or so, the Rust team produces a new Rust *edition*. Each edition brings together the features that have landed into a clear package with fully updated documentation and tooling. New editions ship as part of the usual six-week release process. @@ -32,8 +32,9 @@ landed, which might make Rust worth another look. * For those developing Rust, a new edition provides a rallying point for the project as a whole. -At the time of this writing, three Rust editions are available: Rust 2015, Rust -2018, and Rust 2021. This book is written using Rust 2021 edition idioms. +At the time of this writing, four Rust editions are available: Rust 2015, Rust +2018, Rust 2021, and Rust 2024. This book is written using Rust 2024 edition +idioms. The `edition` key in *Cargo.toml* indicates which edition the compiler should use for your code. If the key doesn’t exist, Rust uses `2015` as the edition @@ -53,14 +54,14 @@ Rust 2018, your project will compile and be able to use that dependency. The opposite situation, where your project uses Rust 2018 and a dependency uses Rust 2015, works as well. -To be clear: most features will be available on all editions. Developers using +To be clear: Most features will be available on all editions. Developers using any Rust edition will continue to see improvements as new stable releases are made. However, in some cases, mainly when new keywords are added, some new features might only be available in later editions. You will need to switch editions if you want to take advantage of such features. -For more details, *The* *Edition Guide* at -*https://doc.rust-lang.org/stable/edition-guide* is a complete book about -editions that enumerates the differences between editions and explains how to -automatically upgrade your code to a new edition via `cargo fix`. +For more details, see *The Rust Edition Guide* at +*https://doc.rust-lang.org/stable/edition-guide*. This is a complete book that +enumerates the differences between editions and explains how to automatically +upgrade your code to a new edition via `cargo fix`. diff --git a/nostarch/chapter10.md b/nostarch/chapter10.md index ba8a9e03bf..29860d9148 100644 --- a/nostarch/chapter10.md +++ b/nostarch/chapter10.md @@ -16,25 +16,25 @@ when compiling and running the code. Functions can take parameters of some generic type, instead of a concrete type like `i32` or `String`, in the same way they take parameters with unknown -values to run the same code on multiple concrete values. In fact, we’ve already +values to run the same code on multiple concrete values. In fact, we already used generics in Chapter 6 with `Option`, in Chapter 8 with `Vec` and `HashMap`, and in Chapter 9 with `Result`. In this chapter, you’ll explore how to define your own types, functions, and methods with generics! -First we’ll review how to extract a function to reduce code duplication. We’ll +First, we’ll review how to extract a function to reduce code duplication. We’ll then use the same technique to make a generic function from two functions that differ only in the types of their parameters. We’ll also explain how to use generic types in struct and enum definitions. -Then you’ll learn how to use *traits* to define behavior in a generic way. You +Then, you’ll learn how to use traits to define behavior in a generic way. You can combine traits with generic types to constrain a generic type to accept only those types that have a particular behavior, as opposed to just any type. Finally, we’ll discuss *lifetimes*: a variety of generics that give the compiler information about how references relate to each other. Lifetimes allow us to give the compiler enough information about borrowed values so that it can -ensure references will be valid in more situations than it could without our -help. +ensure that references will be valid in more situations than it could without +our help. ## Removing Duplication by Extracting a Function @@ -42,7 +42,7 @@ Generics allow us to replace specific types with a placeholder that represents multiple types to remove code duplication. Before diving into generics syntax, let’s first look at how to remove duplication in a way that doesn’t involve generic types by extracting a function that replaces specific values with a -placeholder that represents multiple values. Then we’ll apply the same +placeholder that represents multiple values. Then, we’ll apply the same technique to extract a generic function! By looking at how to recognize duplicated code you can extract into a function, you’ll start to recognize duplicated code that can use generics. @@ -115,7 +115,7 @@ fn main() { Listing 10-2: Code to find the largest number in *two* lists of numbers -Although this code works, duplicating code is tedious and error prone. We also +Although this code works, duplicating code is tedious and error-prone. We also have to remember to update the code in multiple places when we want to change it. @@ -125,7 +125,7 @@ solution makes our code clearer and lets us express the concept of finding the largest number in a list abstractly. In Listing 10-3, we extract the code that finds the largest number into a -function named `largest`. Then we call the function to find the largest number +function named `largest`. Then, we call the function to find the largest number in the two lists from Listing 10-2. We could also use the function on any other list of `i32` values we might have in the future. @@ -185,7 +185,7 @@ values. How would we eliminate that duplication? Let’s find out! We use generics to create definitions for items like function signatures or structs, which we can then use with many different concrete data types. Let’s first look at how to define functions, structs, enums, and methods using -generics. Then we’ll discuss how generics affect code performance. +generics. Then, we’ll discuss how generics affect code performance. ### In Function Definitions @@ -249,13 +249,13 @@ To parameterize the types in a new single function, we need to name the type parameter, just as we do for the value parameters to a function. You can use any identifier as a type parameter name. But we’ll use `T` because, by convention, type parameter names in Rust are short, often just one letter, and -Rust’s type-naming convention is CamelCase. Short for *type*, `T` is the default -choice of most Rust programmers. +Rust’s type-naming convention is UpperCamelCase. Short for *type*, `T` is the +default choice of most Rust programmers. When we use a parameter in the body of the function, we have to declare the -parameter name in the signature so the compiler knows what that name means. -Similarly, when we use a type parameter name in a function signature, we have -to declare the type parameter name before we use it. To define the generic +parameter name in the signature so that the compiler knows what that name +means. Similarly, when we use a type parameter name in a function signature, we +have to declare the type parameter name before we use it. To define the generic `largest` function, we place type name declarations inside angle brackets, `<>`, between the name of the function and the parameter list, like this: @@ -263,8 +263,8 @@ to declare the type parameter name before we use it. To define the generic fn largest(list: &[T]) -> &T { ``` -We read this definition as: the function `largest` is generic over some type -`T`. This function has one parameter named `list`, which is a slice of values +We read this definition as “The function `largest` is generic over some type +`T`.” This function has one parameter named `list`, which is a slice of values of type `T`. The `largest` function will return a reference to a value of the same type `T`. @@ -325,7 +325,7 @@ For more information about this error, try `rustc --explain E0369`. error: could not compile `chapter10` (bin "chapter10") due to 1 previous error ``` -The help text mentions `std::cmp::PartialOrd`, which is a *trait*, and we’re +The help text mentions `std::cmp::PartialOrd`, which is a trait, and we’re going to talk about traits in the next section. For now, know that this error states that the body of `largest` won’t work for all possible types that `T` could be. Because we want to compare values of type `T` in the body, we can @@ -359,10 +359,9 @@ fn main() { Listing 10-6: A `Point` struct that holds `x` and `y` values of type `T` The syntax for using generics in struct definitions is similar to that used in -function definitions. First we declare the name of the type parameter inside -angle brackets just after the name of the struct. Then we use the generic -type in the struct definition where we would otherwise specify concrete data -types. +function definitions. First, we declare the name of the type parameter inside +angle brackets just after the name of the struct. Then, we use the generic type +in the struct definition where we would otherwise specify concrete data types. Note that because we’ve used only one generic type to define `Point`, this definition says that the `Point` struct is generic over some type `T`, and @@ -387,8 +386,8 @@ Listing 10-7: The fields `x` and `y` must be the same type because both have the In this example, when we assign the integer value `5` to `x`, we let the compiler know that the generic type `T` will be an integer for this instance of -`Point`. Then when we specify `4.0` for `y`, which we’ve defined to have the -same type as `x`, we’ll get a type mismatch error like this: +`Point`. Then, when we specify `4.0` for `y`, which we’ve defined to have +the same type as `x`, we’ll get a type mismatch error like this: ``` $ cargo run @@ -506,11 +505,11 @@ Listing 10-9: Implementing a method named `x` on the `Point` struct that will Here, we’ve defined a method named `x` on `Point` that returns a reference to the data in the field `x`. -Note that we have to declare `T` just after `impl` so we can use `T` to specify -that we’re implementing methods on the type `Point`. By declaring `T` as a -generic type after `impl`, Rust can identify that the type in the angle -brackets in `Point` is a generic type rather than a concrete type. We could -have chosen a different name for this generic parameter than the generic +Note that we have to declare `T` just after `impl` so that we can use `T` to +specify that we’re implementing methods on the type `Point`. By declaring +`T` as a generic type after `impl`, Rust can identify that the type in the +angle brackets in `Point` is a generic type rather than a concrete type. We +could have chosen a different name for this generic parameter than the generic parameter declared in the struct definition, but using the same name is conventional. If you write a method within an `impl` that declares a generic type, that method will be defined on any instance of the type, no matter what @@ -518,7 +517,7 @@ concrete type ends up substituting for the generic type. We can also specify constraints on generic types when defining methods on the type. We could, for example, implement methods only on `Point` instances -rather than on `Point` instances with any generic type. In Listing 10-10 we +rather than on `Point` instances with any generic type. In Listing 10-10, we use the concrete type `f32`, meaning we don’t declare any types after `impl`. src/main.rs @@ -541,8 +540,8 @@ available only for floating-point types. Generic type parameters in a struct definition aren’t always the same as those you use in that same struct’s method signatures. Listing 10-11 uses the generic -types `X1` and `Y1` for the `Point` struct and `X2` `Y2` for the `mixup` method -signature to make the example clearer. The method creates a new `Point` +types `X1` and `Y1` for the `Point` struct and `X2` and `Y2` for the `mixup` +method signature to make the example clearer. The method creates a new `Point` instance with the `x` value from the `self` `Point` (of type `X1`) and the `y` value from the passed-in `Point` (of type `Y2`). @@ -573,7 +572,7 @@ fn main() { } ``` -Listing 10-11: A method that uses generic types different from its struct’s definition +Listing 10-11: A method that uses generic types that are different from its struct’s definition In `main`, we’ve defined a `Point` that has an `i32` for `x` (with value `5`) and an `f64` for `y` (with value `10.4`). The `p2` variable is a `Point` struct @@ -600,7 +599,7 @@ Rust accomplishes this by performing monomorphization of the code using generics at compile time. *Monomorphization* is the process of turning generic code into specific code by filling in the concrete types that are used when compiled. In this process, the compiler does the opposite of the steps we used -to create the generic function in Listing 10-5: the compiler looks at all the +to create the generic function in Listing 10-5: The compiler looks at all the places where generic code is called and generates code for the concrete types the generic code is called with. @@ -614,7 +613,7 @@ let float = Some(5.0); When Rust compiles this code, it performs monomorphization. During that process, the compiler reads the values that have been used in `Option` -instances and identifies two kinds of `Option`: one is `i32` and the other +instances and identifies two kinds of `Option`: One is `i32` and the other is `f64`. As such, it expands the generic definition of `Option` into two definitions specialized to `i32` and `f64`, thereby replacing the generic definition with the specific ones. @@ -650,7 +649,11 @@ runs, it performs just as it would if we had duplicated each definition by hand. The process of monomorphization makes Rust’s generics extremely efficient at runtime. -## Traits: Defining Shared Behavior + + + + +## Defining Shared Behavior with Traits A *trait* defines the functionality a particular type has and can share with other types. We can use traits to define shared behavior in an abstract way. We @@ -703,7 +706,7 @@ its own custom behavior for the body of the method. The compiler will enforce that any type that has the `Summary` trait will have the method `summarize` defined with this signature exactly. -A trait can have multiple methods in its body: the method signatures are listed +A trait can have multiple methods in its body: The method signatures are listed one per line, and each line ends in a semicolon. ### Implementing a Trait on a Type @@ -793,16 +796,20 @@ library traits like `Display` on a custom type like `SocialPost` as part of our crate. But we can’t implement external traits on external types. For example, we can’t -implement the `Display` trait on `Vec` within our `aggregator` crate because -`Display` and `Vec` are both defined in the standard library and aren’t -local to our `aggregator` crate. This restriction is part of a property called -*coherence*, and more specifically the *orphan rule*, so named because the -parent type is not present. This rule ensures that other people’s code can’t -break your code and vice versa. Without the rule, two crates could implement -the same trait for the same type, and Rust wouldn’t know which implementation -to use. +implement the `Display` trait on `Vec` within our `aggregator` crate, +because `Display` and `Vec` are both defined in the standard library and +aren’t local to our `aggregator` crate. This restriction is part of a property +called *coherence*, and more specifically the *orphan rule*, so named because +the parent type is not present. This rule ensures that other people’s code +can’t break your code and vice versa. Without the rule, two crates could +implement the same trait for the same type, and Rust wouldn’t know which +implementation to use. + + + + -### Default Implementations +### Using Default Implementations Sometimes it’s useful to have default behavior for some or all of the methods in a trait instead of requiring implementations for all methods on every type. @@ -909,7 +916,11 @@ This code prints `1 new post: (Read more from @horse_ebooks...)`. Note that it isn’t possible to call the default implementation from an overriding implementation of that same method. -### Traits as Parameters + + + + +### Using Traits as Parameters Now that you know how to define and implement traits, we can explore how to use traits to define functions that accept many different types. We’ll use the @@ -929,7 +940,7 @@ keyword and the trait name. This parameter accepts any type that implements the specified trait. In the body of `notify`, we can call any methods on `item` that come from the `Summary` trait, such as `summarize`. We can call `notify` and pass in any instance of `NewsArticle` or `SocialPost`. Code that calls the -function with any other type, such as a `String` or an `i32`, won’t compile +function with any other type, such as a `String` or an `i32`, won’t compile, because those types don’t implement `Summary`. @@ -973,10 +984,14 @@ The generic type `T` specified as the type of the `item1` and `item2` parameters constrains the function such that the concrete type of the value passed as an argument for `item1` and `item2` must be the same. -#### Specifying Multiple Trait Bounds with the + Syntax + + + + +#### Multiple Trait Bounds with the + Syntax We can also specify more than one trait bound. Say we wanted `notify` to use -display formatting as well as `summarize` on `item`: we specify in the `notify` +display formatting as well as `summarize` on `item`: We specify in the `notify` definition that `item` must implement both `Display` and `Summary`. We can do so using the `+` syntax: @@ -1016,7 +1031,7 @@ where { ``` -This function’s signature is less cluttered: the function name, parameter list, +This function’s signature is less cluttered: The function name, parameter list, and return type are close together, similar to a function without lots of trait bounds. @@ -1085,20 +1100,20 @@ fn returns_summarizable(switch: bool) -> impl Summary { Returning either a `NewsArticle` or a `SocialPost` isn’t allowed due to restrictions around how the `impl Trait` syntax is implemented in the compiler. We’ll cover how to write a function with this behavior in the “Using Trait -Objects That Allow for Values of Different -Types” section of Chapter 18. +Objects to Abstract over Shared Behavior” +section of Chapter 18. ### Using Trait Bounds to Conditionally Implement Methods By using a trait bound with an `impl` block that uses generic type parameters, we can implement methods conditionally for types that implement the specified traits. For example, the type `Pair` in Listing 10-15 always implements the -`new` function to return a new instance of `Pair` (recall from the -“Defining Methods” section of Chapter 5 that `Self` -is a type alias for the type of the `impl` block, which in this case is -`Pair`). But in the next `impl` block, `Pair` only implements the -`cmp_display` method if its inner type `T` implements the `PartialOrd` trait -that enables comparison *and* the `Display` trait that enables printing. +`new` function to return a new instance of `Pair` (recall from the “Method +Syntax” section of Chapter 5 that `Self` is a type +alias for the type of the `impl` block, which in this case is `Pair`). But +in the next `impl` block, `Pair` only implements the `cmp_display` method if +its inner type `T` implements the `PartialOrd` trait that enables comparison +*and* the `Display` trait that enables printing. src/lib.rs @@ -1159,10 +1174,10 @@ reduce duplication but also specify to the compiler that we want the generic type to have particular behavior. The compiler can then use the trait bound information to check that all the concrete types used with our code provide the correct behavior. In dynamically typed languages, we would get an error at -runtime if we called a method on a type which didn’t define the method. But -Rust moves these errors to compile time so we’re forced to fix the problems +runtime if we called a method on a type that didn’t define the method. But Rust +moves these errors to compile time so that we’re forced to fix the problems before our code is even able to run. Additionally, we don’t have to write code -that checks for behavior at runtime because we’ve already checked at compile +that checks for behavior at runtime, because we’ve already checked at compile time. Doing so improves performance without having to give up the flexibility of generics. @@ -1174,26 +1189,30 @@ references are valid as long as we need them to be. One detail we didn’t discuss in the “References and Borrowing” section in Chapter 4 is -that every reference in Rust has a *lifetime*, which is the scope for which +that every reference in Rust has a lifetime, which is the scope for which that reference is valid. Most of the time, lifetimes are implicit and inferred, just like most of the time, types are inferred. We are only required to -annotate types when multiple types are possible. In a similar way, we have to +annotate types when multiple types are possible. In a similar way, we must annotate lifetimes when the lifetimes of references could be related in a few different ways. Rust requires us to annotate the relationships using generic -lifetime parameters to ensure the actual references used at runtime will +lifetime parameters to ensure that the actual references used at runtime will definitely be valid. Annotating lifetimes is not even a concept most other programming languages have, so this is going to feel unfamiliar. Although we won’t cover lifetimes in their entirety in this chapter, we’ll discuss common ways you might encounter -lifetime syntax so you can get comfortable with the concept. +lifetime syntax so that you can get comfortable with the concept. -### Preventing Dangling References with Lifetimes + + + -The main aim of lifetimes is to prevent *dangling references*, which cause a -program to reference data other than the data it’s intended to reference. -Consider the program in Listing 10-16, which has an outer scope and an inner -scope. +### Dangling References + +The main aim of lifetimes is to prevent dangling references, which, if they +were allowed to exist, would cause a program to reference data other than the +data it’s intended to reference. Consider the program in Listing 10-16, which +has an outer scope and an inner scope. ``` @@ -1213,17 +1232,17 @@ Listing 10-16: An attempt to use a reference whose value has gone out of scope > Note: The examples in Listings 10-16, 10-17, and 10-23 declare variables > without giving them an initial value, so the variable name exists in the outer -> scope. At first glance, this might appear to be in conflict with Rust’s having +> scope. At first glance, this might appear to be in conflict with Rust having > no null values. However, if we try to use a variable before giving it a value, -> we’ll get a compile-time error, which shows that Rust indeed does not allow +> we’ll get a compile-time error, which shows that indeed Rust does not allow > null values. The outer scope declares a variable named `r` with no initial value, and the inner scope declares a variable named `x` with the initial value of `5`. Inside -the inner scope, we attempt to set the value of `r` as a reference to `x`. Then -the inner scope ends, and we attempt to print the value in `r`. This code won’t -compile because the value that `r` is referring to has gone out of scope before -we try to use it. Here is the error message: +the inner scope, we attempt to set the value of `r` as a reference to `x`. +Then, the inner scope ends, and we attempt to print the value in `r`. This code +won’t compile, because the value that `r` is referring to has gone out of scope +before we try to use it. Here is the error message: ``` $ cargo run @@ -1250,7 +1269,7 @@ reason is that `x` will be out of scope when the inner scope ends on line 7. But `r` is still valid for the outer scope; because its scope is larger, we say that it “lives longer.” If Rust allowed this code to work, `r` would be referencing memory that was deallocated when `x` went out of scope, and -anything we tried to do with `r` wouldn’t work correctly. So how does Rust +anything we tried to do with `r` wouldn’t work correctly. So, how does Rust determine that this code is invalid? It uses a borrow checker. ### The Borrow Checker @@ -1280,10 +1299,10 @@ with `'b`. As you can see, the inner `'b` block is much smaller than the outer `'a` lifetime block. At compile time, Rust compares the size of the two lifetimes and sees that `r` has a lifetime of `'a` but that it refers to memory with a lifetime of `'b`. The program is rejected because `'b` is shorter than -`'a`: the subject of the reference doesn’t live as long as the reference. +`'a`: The subject of the reference doesn’t live as long as the reference. -Listing 10-18 fixes the code so it doesn’t have a dangling reference and it -compiles without any errors. +Listing 10-18 fixes the code so that it doesn’t have a dangling reference and +it compiles without any errors. ``` @@ -1304,8 +1323,8 @@ means `r` can reference `x` because Rust knows that the reference in `r` will always be valid while `x` is valid. Now that you know where the lifetimes of references are and how Rust analyzes -lifetimes to ensure references will always be valid, let’s explore generic -lifetimes of parameters and return values in the context of functions. +lifetimes to ensure that references will always be valid, let’s explore generic +lifetimes in function parameters and return values. ### Generic Lifetimes in Functions @@ -1383,7 +1402,7 @@ Listings 10-17 and 10-18 to determine whether the reference we return will always be valid. The borrow checker can’t determine this either, because it doesn’t know how the lifetimes of `x` and `y` relate to the lifetime of the return value. To fix this error, we’ll add generic lifetime parameters that -define the relationship between the references so the borrow checker can +define the relationship between the references so that the borrow checker can perform its analysis. ### Lifetime Annotation Syntax @@ -1394,15 +1413,15 @@ other without affecting the lifetimes. Just as functions can accept any type when the signature specifies a generic type parameter, functions can accept references with any lifetime by specifying a generic lifetime parameter. -Lifetime annotations have a slightly unusual syntax: the names of lifetime +Lifetime annotations have a slightly unusual syntax: The names of lifetime parameters must start with an apostrophe (`'`) and are usually all lowercase and very short, like generic types. Most people use the name `'a` for the first lifetime annotation. We place lifetime parameter annotations after the `&` of a reference, using a space to separate the annotation from the reference’s type. -Here are some examples: a reference to an `i32` without a lifetime parameter, a +Here are some examples—a reference to an `i32` without a lifetime parameter, a reference to an `i32` that has a lifetime parameter named `'a`, and a mutable -reference to an `i32` that also has the lifetime `'a`. +reference to an `i32` that also has the lifetime `'a`: ``` &i32 // a reference @@ -1410,22 +1429,26 @@ reference to an `i32` that also has the lifetime `'a`. &'a mut i32 // a mutable reference with an explicit lifetime ``` -One lifetime annotation by itself doesn’t have much meaning because the +One lifetime annotation by itself doesn’t have much meaning, because the annotations are meant to tell Rust how generic lifetime parameters of multiple references relate to each other. Let’s examine how the lifetime annotations relate to each other in the context of the `longest` function. -### Lifetime Annotations in Function Signatures + + + + +### In Function Signatures To use lifetime annotations in function signatures, we need to declare the -generic *lifetime* parameters inside angle brackets between the function name -and the parameter list, just as we did with generic *type* parameters. +generic lifetime parameters inside angle brackets between the function name and +the parameter list, just as we did with generic type parameters. -We want the signature to express the following constraint: the returned -reference will be valid as long as both the parameters are valid. This is the -relationship between lifetimes of the parameters and the return value. We’ll -name the lifetime `'a` and then add it to each reference, as shown in Listing -10-21. +We want the signature to express the following constraint: The returned +reference will be valid as long as both of the parameters are valid. This is +the relationship between lifetimes of the parameters and the return value. +We’ll name the lifetime `'a` and then add it to each reference, as shown in +Listing 10-21. src/main.rs @@ -1504,7 +1527,7 @@ Next, let’s try an example that shows that the lifetime of the reference in `result` must be the smaller lifetime of the two arguments. We’ll move the declaration of the `result` variable outside the inner scope but leave the assignment of the value to the `result` variable inside the scope with -`string2`. Then we’ll move the `println!` that uses `result` to outside the +`string2`. Then, we’ll move the `println!` that uses `result` to outside the inner scope, after the inner scope has ended. The code in Listing 10-23 will not compile. @@ -1562,9 +1585,13 @@ disallows the code in Listing 10-23 as possibly having an invalid reference. Try designing more experiments that vary the values and lifetimes of the references passed in to the `longest` function and how the returned reference is used. Make hypotheses about whether or not your experiments will pass the -borrow checker before you compile; then check to see if you’re right! +borrow checker before you compile; then, check to see if you’re right! + + -### Thinking in Terms of Lifetimes + + +### Relationships The way in which you need to specify lifetime parameters depends on what your function is doing. For example, if we changed the implementation of the @@ -1631,7 +1658,7 @@ of the `longest` function. We’re also trying to return a reference to `result` from the function. There is no way we can specify lifetime parameters that would change the dangling reference, and Rust won’t let us create a dangling reference. In this case, the best fix would be to return an owned data type -rather than a reference so the calling function is then responsible for +rather than a reference so that the calling function is then responsible for cleaning up the value. Ultimately, lifetime syntax is about connecting the lifetimes of various @@ -1639,12 +1666,16 @@ parameters and return values of functions. Once they’re connected, Rust has enough information to allow memory-safe operations and disallow operations that would create dangling pointers or otherwise violate memory safety. -### Lifetime Annotations in Struct Definitions + + + + +### In Struct Definitions So far, the structs we’ve defined all hold owned types. We can define structs -to hold references, but in that case we would need to add a lifetime annotation -on every reference in the struct’s definition. Listing 10-24 has a struct named -`ImportantExcerpt` that holds a string slice. +to hold references, but in that case, we would need to add a lifetime +annotation on every reference in the struct’s definition. Listing 10-24 has a +struct named `ImportantExcerpt` that holds a string slice. src/main.rs @@ -1666,8 +1697,8 @@ Listing 10-24: A struct that holds a reference, requiring a lifetime annotation This struct has the single field `part` that holds a string slice, which is a reference. As with generic data types, we declare the name of the generic -lifetime parameter inside angle brackets after the name of the struct so we can -use the lifetime parameter in the body of the struct definition. This +lifetime parameter inside angle brackets after the name of the struct so that +we can use the lifetime parameter in the body of the struct definition. This annotation means an instance of `ImportantExcerpt` can’t outlive the reference it holds in its `part` field. @@ -1704,7 +1735,7 @@ fn first_word(s: &str) -> &str { Listing 10-25: A function we defined in Listing 4-9 that compiled without lifetime annotations, even though the parameter and return type are references The reason this function compiles without lifetime annotations is historical: -in early versions (pre-1.0) of Rust, this code wouldn’t have compiled because +In early versions (pre-1.0) of Rust, this code wouldn’t have compiled, because every reference needed an explicit lifetime. At that time, the function signature would have been written like this: @@ -1716,8 +1747,8 @@ After writing a lot of Rust code, the Rust team found that Rust programmers were entering the same lifetime annotations over and over in particular situations. These situations were predictable and followed a few deterministic patterns. The developers programmed these patterns into the compiler’s code so -the borrow checker could infer the lifetimes in these situations and wouldn’t -need explicit annotations. +that the borrow checker could infer the lifetimes in these situations and +wouldn’t need explicit annotations. This piece of Rust history is relevant because it’s possible that more deterministic patterns will emerge and be added to the compiler. In the future, @@ -1731,8 +1762,8 @@ fits these cases, you don’t need to write the lifetimes explicitly. The elision rules don’t provide full inference. If there is still ambiguity about what lifetimes the references have after Rust applies the rules, the compiler won’t guess what the lifetime of the remaining references should be. -Instead of guessing, the compiler will give you an error that you can resolve by -adding the lifetime annotations. +Instead of guessing, the compiler will give you an error that you can resolve +by adding the lifetime annotations. Lifetimes on function or method parameters are called *input lifetimes*, and lifetimes on return values are called *output lifetimes*. @@ -1766,7 +1797,7 @@ references: fn first_word(s: &str) -> &str { ``` -Then the compiler applies the first rule, which specifies that each parameter +Then, the compiler applies the first rule, which specifies that each parameter gets its own lifetime. We’ll call it `'a` as usual, so now the signature is this: @@ -1793,31 +1824,35 @@ no lifetime parameters when we started working with it in Listing 10-20: fn longest(x: &str, y: &str) -> &str { ``` -Let’s apply the first rule: each parameter gets its own lifetime. This time we +Let’s apply the first rule: Each parameter gets its own lifetime. This time we have two parameters instead of one, so we have two lifetimes: ``` fn longest<'a, 'b>(x: &'a str, y: &'b str) -> &str { ``` -You can see that the second rule doesn’t apply because there is more than one +You can see that the second rule doesn’t apply, because there is more than one input lifetime. The third rule doesn’t apply either, because `longest` is a function rather than a method, so none of the parameters are `self`. After working through all three rules, we still haven’t figured out what the return type’s lifetime is. This is why we got an error trying to compile the code in -Listing 10-20: the compiler worked through the lifetime elision rules but still +Listing 10-20: The compiler worked through the lifetime elision rules but still couldn’t figure out all the lifetimes of the references in the signature. Because the third rule really only applies in method signatures, we’ll look at lifetimes in that context next to see why the third rule means we don’t have to annotate lifetimes in method signatures very often. -### Lifetime Annotations in Method Definitions + + + + +### In Method Definitions When we implement methods on a struct with lifetimes, we use the same syntax as -that of generic type parameters, as shown in Listing 10-11. Where we declare and -use the lifetime parameters depends on whether they’re related to the struct -fields or the method parameters and return values. +that of generic type parameters, as shown in Listing 10-11. Where we declare +and use the lifetime parameters depends on whether they’re related to the +struct fields or the method parameters and return values. Lifetime names for struct fields always need to be declared after the `impl` keyword and then used after the struct’s name because those lifetimes are part @@ -1829,7 +1864,7 @@ addition, the lifetime elision rules often make it so that lifetime annotations aren’t necessary in method signatures. Let’s look at some examples using the struct named `ImportantExcerpt` that we defined in Listing 10-24. -First we’ll use a method named `level` whose only parameter is a reference to +First, we’ll use a method named `level` whose only parameter is a reference to `self` and whose return value is an `i32`, which is not a reference to anything: ``` @@ -1841,8 +1876,8 @@ impl<'a> ImportantExcerpt<'a> { ``` The lifetime parameter declaration after `impl` and its use after the type name -are required, but we’re not required to annotate the lifetime of the reference -to `self` because of the first elision rule. +are required, but because of the first elision rule, we’re not required to +annotate the lifetime of the reference to `self`. Here is an example where the third lifetime elision rule applies: @@ -1875,13 +1910,17 @@ always available. Therefore, the lifetime of all string literals is `'static`. You might see suggestions in error messages to use the `'static` lifetime. But before specifying `'static` as the lifetime for a reference, think about -whether the reference you have actually lives the entire lifetime of your -program or not, and whether you want it to. Most of the time, an error message +whether or not the reference you have actually lives the entire lifetime of +your program, and whether you want it to. Most of the time, an error message suggesting the `'static` lifetime results from attempting to create a dangling reference or a mismatch of the available lifetimes. In such cases, the solution is to fix those problems, not to specify the `'static` lifetime. -## Generic Type Parameters, Trait Bounds, and Lifetimes Together + + + + +## Generic Type Parameters, Trait Bounds, and Lifetimes Let’s briefly look at the syntax of specifying generic type parameters, trait bounds, and lifetimes all in one function! @@ -1926,5 +1965,5 @@ Believe it or not, there is much more to learn on the topics we discussed in this chapter: Chapter 18 discusses trait objects, which are another way to use traits. There are also more complex scenarios involving lifetime annotations that you will only need in very advanced scenarios; for those, you should read -the Rust Reference at *../reference/index.html*. But next, you’ll learn how to write tests in -Rust so you can make sure your code is working the way it should. +the Rust Reference at *../reference/trait-bounds.html*. But next, you’ll learn how to write tests in +Rust so that you can make sure your code is working the way it should. diff --git a/nostarch/chapter11.md b/nostarch/chapter11.md index 6363cfddc7..f90efc3fe1 100644 --- a/nostarch/chapter11.md +++ b/nostarch/chapter11.md @@ -13,11 +13,12 @@ testing can be a very effective way to show the presence of bugs, but it is hopelessly inadequate for showing their absence.” That doesn’t mean we shouldn’t try to test as much as we can! -Correctness in our programs is the extent to which our code does what we intend -it to do. Rust is designed with a high degree of concern about the correctness -of programs, but correctness is complex and not easy to prove. Rust’s type -system shoulders a huge part of this burden, but the type system cannot catch -everything. As such, Rust includes support for writing automated software tests. +*Correctness* in our programs is the extent to which our code does what we +intend it to do. Rust is designed with a high degree of concern about the +correctness of programs, but correctness is complex and not easy to prove. +Rust’s type system shoulders a huge part of this burden, but the type system +cannot catch everything. As such, Rust includes support for writing automated +software tests. Say we write a function `add_two` that adds 2 to whatever number is passed to it. This function’s signature accepts an integer as a parameter and returns an @@ -33,7 +34,7 @@ We can write tests that assert, for example, that when we pass `3` to the we make changes to our code to make sure any existing correct behavior has not changed. -Testing is a complex skill: although we can’t cover in one chapter every detail +Testing is a complex skill: Although we can’t cover in one chapter every detail about how to write good tests, in this chapter we will discuss the mechanics of Rust’s testing facilities. We’ll talk about the annotations and macros available to you when writing your tests, the default behavior and options @@ -42,7 +43,7 @@ integration tests. ## How to Write Tests -Tests are Rust functions that verify that the non-test code is functioning in +*Tests* are Rust functions that verify that the non-test code is functioning in the expected manner. The bodies of test functions typically perform these three actions: @@ -54,7 +55,11 @@ Let’s look at the features Rust provides specifically for writing tests that take these actions, which include the `test` attribute, a few macros, and the `should_panic` attribute. -### The Anatomy of a Test Function + + + + +### Structuring Test Functions At its simplest, a test in Rust is a function that’s annotated with the `test` attribute. Attributes are metadata about pieces of Rust code; one example is @@ -66,12 +71,12 @@ fails. Whenever we make a new library project with Cargo, a test module with a test function in it is automatically generated for us. This module gives you a -template for writing your tests so you don’t have to look up the exact +template for writing your tests so that you don’t have to look up the exact structure and syntax every time you start a new project. You can add as many additional test functions and as many test modules as you want! We’ll explore some aspects of how tests work by experimenting with the template -test before we actually test any code. Then we’ll write some real-world tests +test before we actually test any code. Then, we’ll write some real-world tests that call some code that we’ve written and assert that its behavior is correct. Let’s create a new library project called `adder` that will add two numbers: @@ -117,11 +122,11 @@ mod tests { Listing 11-1: The code generated automatically by `cargo new` -The file starts with an example `add` function, so that we have something -to test. +The file starts with an example `add` function so that we have something to +test. For now, let’s focus solely on the `it_works` function. Note the `#[test]` -annotation: this attribute indicates this is a test function, so the test +annotation: This attribute indicates this is a test function, so the test runner knows to treat this function as a test. We might also have non-test functions in the `tests` module to help set up common scenarios or perform common operations, so we always need to indicate which functions are tests. @@ -160,13 +165,13 @@ Cargo compiled and ran the test. We see the line `running 1 test`. The next line shows the name of the generated test function, called `tests::it_works`, and that the result of running that test is `ok`. The overall summary `test result: ok.` means that all the tests passed, and the portion that reads `1 passed; 0 failed` totals the number of tests that passed or failed. -It’s possible to mark a test as ignored so it doesn’t run in a particular -instance; we’ll cover that in the “Ignoring Some Tests Unless Specifically +It’s possible to mark a test as ignored so that it doesn’t run in a particular +instance; we’ll cover that in the “Ignoring Tests Unless Specifically Requested” section later in this chapter. Because we haven’t done that here, the summary shows `0 ignored`. We can also pass an argument to the `cargo test` command to run only tests whose name matches a -string; this is called *filtering* and we’ll cover it in the “Running a -Subset of Tests by Name” section. Here we haven’t +string; this is called *filtering*, and we’ll cover it in the “Running a +Subset of Tests by Name” section. Here, we haven’t filtered the tests being run, so the end of the summary shows `0 filtered out`. The `0 measured` statistic is for benchmark tests that measure performance. @@ -203,7 +208,7 @@ mod tests { } ``` -Then run `cargo test` again. The output now shows `exploration` instead of +Then, run `cargo test` again. The output now shows `exploration` instead of `it_works`: ``` @@ -297,22 +302,26 @@ check the line number of the panic matches the line number in the following para --> Instead of `ok`, the line `test tests::another` shows `FAILED`. Two new -sections appear between the individual results and the summary: the first +sections appear between the individual results and the summary: The first displays the detailed reason for each test failure. In this case, we get the details that `tests::another` failed because it panicked with the message `Make this test fail` on line 17 in the *src/lib.rs* file. The next section lists just the names of all the failing tests, which is useful when there are lots of tests and lots of detailed failing test output. We can use the name of a -failing test to run just that test to more easily debug it; we’ll talk more +failing test to run just that test to debug it more easily; we’ll talk more about ways to run tests in the “Controlling How Tests Are Run” section. -The summary line displays at the end: overall, our test result is `FAILED`. We +The summary line displays at the end: Overall, our test result is `FAILED`. We had one test pass and one test fail. Now that you’ve seen what the test results look like in different scenarios, let’s look at some macros other than `panic!` that are useful in tests. -### Checking Results with the assert! Macro + + + + +### Checking Results with assert! The `assert!` macro, provided by the standard library, is useful when you want to ensure that some condition in a test evaluates to `true`. We give the @@ -384,7 +393,7 @@ a glob here, so anything we define in the outer module is available to this `tests` module. We’ve named our test `larger_can_hold_smaller`, and we’ve created the two -`Rectangle` instances that we need. Then we called the `assert!` macro and +`Rectangle` instances that we need. Then, we called the `assert!` macro and passed it the result of calling `larger.can_hold(&smaller)`. This expression is supposed to return `true`, so our test should pass. Let’s find out! @@ -464,8 +473,8 @@ test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; fini Two tests that pass! Now let’s see what happens to our test results when we introduce a bug in our code. We’ll change the implementation of the `can_hold` -method by replacing the greater-than sign with a less-than sign when it -compares the widths: +method by replacing the greater-than sign (`>`) with a less-than sign (`<`) +when it compares the widths: ``` // --snip-- @@ -509,7 +518,11 @@ Our tests caught the bug! Because `larger.width` is `8` and `smaller.width` is `5`, the comparison of the widths in `can_hold` now returns `false`: 8 is not less than 5. -### Testing Equality with the assert_eq! and assert_ne! Macros + + + + +### Testing Equality with assert_eq! and assert_ne! A common way to verify functionality is to test for equality between the result of the code under test and the value you expect the code to return. You could @@ -523,7 +536,7 @@ fails, which makes it easier to see *why* the test failed; conversely, the expression, without printing the values that led to the `false` value. In Listing 11-7, we write a function named `add_two` that adds `2` to its -parameter, then we test this function using the `assert_eq!` macro. +parameter, and then we test this function using the `assert_eq!` macro. src/lib.rs @@ -568,7 +581,7 @@ test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; fini ``` We create a variable named `result` that holds the result of calling -`add_two(2)`. Then we pass `result` and `4` as the arguments to the +`add_two(2)`. Then, we pass `result` and `4` as the arguments to the `assert_eq!` macro. The output line for this test is `test tests::it_adds_two ... ok`, and the `ok` text indicates that our test passed! Let’s introduce a bug into our code to see what `assert_eq!` looks like when it @@ -612,8 +625,8 @@ error: test failed, to rerun pass `--lib` Our test caught the bug! The `tests::it_adds_two` test failed, and the message tells us that the assertion that failed was `left == right` and what the `left` -and `right` values are. This message helps us start debugging: the `left` -argument, where we had the result of calling `add_two(2)`, was `5` but the +and `right` values are. This message helps us start debugging: The `left` +argument, where we had the result of calling `add_two(2)`, was `5`, but the `right` argument was `4`. You can imagine that this would be especially helpful when we have a lot of tests going on. @@ -623,14 +636,14 @@ we specify the arguments matters. However, in Rust, they’re called `left` and `right`, and the order in which we specify the value we expect and the value the code produces doesn’t matter. We could write the assertion in this test as `assert_eq!(4, result)`, which would result in the same failure message that -displays `` assertion `left == right` failed``. +displays ``assertion `left == right` failed``. The `assert_ne!` macro will pass if the two values we give it are not equal and -fail if they’re equal. This macro is most useful for cases when we’re not sure -what a value *will* be, but we know what the value definitely *shouldn’t* be. -For example, if we’re testing a function that is guaranteed to change its input -in some way, but the way in which the input is changed depends on the day of -the week that we run our tests, the best thing to assert might be that the +will fail if they are equal. This macro is most useful for cases when we’re not +sure what a value *will* be, but we know what the value definitely *shouldn’t* +be. For example, if we’re testing a function that is guaranteed to change its +input in some way, but the way in which the input is changed depends on the day +of the week that we run our tests, the best thing to assert might be that the output of the function is not equal to the input. Under the surface, the `assert_eq!` and `assert_ne!` macros use the operators @@ -651,8 +664,8 @@ details about these and other derivable traits. You can also add a custom message to be printed with the failure message as optional arguments to the `assert!`, `assert_eq!`, and `assert_ne!` macros. Any arguments specified after the required arguments are passed along to the -`format!` macro (discussed in “Concatenation with the `+` Operator or the -`format!` Macro” in Chapter 8), so you can pass a format string that contains `{}` +`format!` macro (discussed in “Concatenating with `+` or +`format!`” in Chapter 8), so you can pass a format string that contains `{}` placeholders and values to go in those placeholders. Custom messages are useful for documenting what an assertion means; when a test fails, you’ll have a better idea of what the problem is with the code. @@ -984,15 +997,19 @@ error: test failed, to rerun pass `--lib` ``` The failure message indicates that this test did indeed panic as we expected, -but the panic message did not include the expected string `less than or equal to 100`. The panic message that we did get in this case was `Guess value must be greater than or equal to 1, got 200.` Now we can start figuring out where +but the panic message did not include the expected string `less than or equal to 100`. The panic message that we did get in this case was `Guess value must be greater than or equal to 1, got 200`. Now we can start figuring out where our bug is! ### Using Result in Tests -Our tests so far all panic when they fail. We can also write tests that use +All of our tests so far panic when they fail. We can also write tests that use `Result`! Here’s the test from Listing 11-1, rewritten to use `Result` and return an `Err` instead of panicking: ``` +#[cfg(test)] +mod tests { + use super::*; + #[test] fn it_works() -> Result<(), String> { let result = add(2, 2); @@ -1003,6 +1020,7 @@ Our tests so far all panic when they fail. We can also write tests that use Err(String::from("two plus two does not equal four")) } } +} ``` The `it_works` function now has the `Result<(), String>` return type. In the @@ -1010,9 +1028,10 @@ body of the function, rather than calling the `assert_eq!` macro, we return `Ok(())` when the test passes and an `Err` with a `String` inside when the test fails. -Writing tests so they return a `Result` enables you to use the question -mark operator in the body of tests, which can be a convenient way to write -tests that should fail if any operation within them returns an `Err` variant. +Writing tests so that they return a `Result` enables you to use the +question mark operator in the body of tests, which can be a convenient way to +write tests that should fail if any operation within them returns an `Err` +variant. You can’t use the `#[should_panic]` annotation on tests that use `Result`. To assert that an operation returns an `Err` variant, *don’t* use the question mark operator on the `Result` value. Instead, use @@ -1036,26 +1055,26 @@ binary. To separate these two types of arguments, you list the arguments that go to `cargo test` followed by the separator `--` and then the ones that go to the test binary. Running `cargo test --help` displays the options you can use with `cargo test`, and running `cargo test -- --help` displays the options you -can use after the separator. Those options are also documented in the “Tests” -section at *https://doc.rust-lang.org/rustc/tests/index.html* of the the rustc book at *https://doc.rust-lang.org/rustc/index.html*. +can use after the separator. These options are also documented in the “Tests” +section of *The `rustc` Book* at *https://doc.rust-lang.org/rustc/tests/index.html*. ### Running Tests in Parallel or Consecutively When you run multiple tests, by default they run in parallel using threads, -meaning they finish running faster and you get feedback quicker. Because the -tests are running at the same time, you must make sure your tests don’t depend -on each other or on any shared state, including a shared environment, such as -the current working directory or environment variables. +meaning they finish running more quickly and you get feedback sooner. Because +the tests are running at the same time, you must make sure your tests don’t +depend on each other or on any shared state, including a shared environment, +such as the current working directory or environment variables. For example, say each of your tests runs some code that creates a file on disk -named *test-output.txt* and writes some data to that file. Then each test reads -the data in that file and asserts that the file contains a particular value, -which is different in each test. Because the tests run at the same time, one -test might overwrite the file in the time between another test writing and -reading the file. The second test will then fail, not because the code is -incorrect but because the tests have interfered with each other while running -in parallel. One solution is to make sure each test writes to a different file; -another solution is to run the tests one at a time. +named *test-output.txt* and writes some data to that file. Then, each test +reads the data in that file and asserts that the file contains a particular +value, which is different in each test. Because the tests run at the same time, +one test might overwrite the file in the time between when another test is +writing and reading the file. The second test will then fail, not because the +code is incorrect but because the tests have interfered with each other while +running in parallel. One solution is to make sure each test writes to a +different file; another solution is to run the tests one at a time. If you don’t want to run the tests in parallel or if you want more fine-grained control over the number of threads used, you can send the `--test-threads` flag @@ -1198,7 +1217,7 @@ error: test failed, to rerun pass `--lib` ### Running a Subset of Tests by Name -Sometimes, running a full test suite can take a long time. If you’re working on +Running a full test suite can sometimes take a long time. If you’re working on code in a particular area, you might want to run only the tests pertaining to that code. You can choose which tests to run by passing `cargo test` the name or names of the test(s) you want to run as an argument. @@ -1312,7 +1331,11 @@ named `one_hundred`. Also note that the module in which a test appears becomes part of the test’s name, so we can run all the tests in a module by filtering on the module’s name. -### Ignoring Some Tests Unless Specifically Requested + + + + +### Ignoring Tests Unless Specifically Requested Sometimes a few specific tests can be very time-consuming to execute, so you might want to exclude them during most runs of `cargo test`. Rather than @@ -1415,7 +1438,7 @@ code that they’re testing. The convention is to create a module named `tests` in each file to contain the test functions and to annotate the module with `cfg(test)`. -#### The Tests Module and \#[cfg(test)] +#### The tests Module and \#[cfg(test)] The `#[cfg(test)]` annotation on the `tests` module tells Rust to compile and run the test code only when you run `cargo test`, not when you run `cargo build`. This saves compile time when you only want to build the library and @@ -1455,7 +1478,11 @@ given a certain configuration option. In this case, the configuration option is with `cargo test`. This includes any helper functions that might be within this module, in addition to the functions annotated with `#[test]`. -#### Testing Private Functions + + + + +#### Private Function Tests There’s debate within the testing community about whether or not private functions should be tested directly, and other languages make it difficult or @@ -1492,9 +1519,10 @@ Note that the `internal_adder` function is not marked as `pub`. Tests are just Rust code, and the `tests` module is just another module. As we discussed in “Paths for Referring to an Item in the Module Tree”, items in child modules can use the items in their ancestor modules. In this -test, we bring all of the `tests` module’s parent’s items into scope with `use super::*`, and then the test can call `internal_adder`. If you don’t think -private functions should be tested, there’s nothing in Rust that will compel you -to do so. +test, we bring all of the items belonging to the `tests` module’s parent into +scope with `use super::*`, and then the test can call `internal_adder`. If you +don’t think private functions should be tested, there’s nothing in Rust that +will compel you to do so. ### Integration Tests @@ -1544,7 +1572,7 @@ fn it_adds_two() { Listing 11-13: An integration test of a function in the `adder` crate Each file in the *tests* directory is a separate crate, so we need to bring our -library into each test crate’s scope. For that reason we add `use adder::add_two;` at the top of the code, which we didn’t need in the unit tests. +library into each test crate’s scope. For that reason, we add `use adder::add_two;` at the top of the code, which we didn’t need in the unit tests. We don’t need to annotate any code in *tests/integration_test.rs* with `#[cfg(test)]`. Cargo treats the *tests* directory specially and compiles files @@ -1579,7 +1607,7 @@ test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; fini The three sections of output include the unit tests, the integration test, and the doc tests. Note that if any test in a section fails, the following sections will not be run. For example, if a unit test fails, there won’t be any output -for integration and doc tests because those tests will only be run if all unit +for integration and doc tests, because those tests will only be run if all unit tests are passing. The first section for the unit tests is the same as we’ve been seeing: one line @@ -1625,7 +1653,7 @@ share the same behavior as files in *src* do, as you learned in Chapter 7 regarding how to separate code into modules and files. The different behavior of *tests* directory files is most noticeable when you -have a set of helper functions to use in multiple integration test files and +have a set of helper functions to use in multiple integration test files, and you try to follow the steps in the “Separating Modules into Different Files” section of Chapter 7 to extract them into a common module. For example, if we create *tests/common.rs* @@ -1743,8 +1771,8 @@ file will work as well, and that small amount of code doesn’t need to be teste ## Summary Rust’s testing features provide a way to specify how code should function to -ensure it continues to work as you expect, even as you make changes. Unit tests -exercise different parts of a library separately and can test private +ensure that it continues to work as you expect, even as you make changes. Unit +tests exercise different parts of a library separately and can test private implementation details. Integration tests check that many parts of the library work together correctly, and they use the library’s public API to test the code in the same way external code will use it. Even though Rust’s type system and diff --git a/nostarch/chapter12.md b/nostarch/chapter12.md index dbb7242d83..7b22854400 100644 --- a/nostarch/chapter12.md +++ b/nostarch/chapter12.md @@ -18,7 +18,7 @@ an ideal language for creating command line tools, so for our project, we’ll make our own version of the classic command line search tool `grep` (**g**lobally search a **r**egular **e**xpression and **p**rint). In the simplest use case, `grep` searches a specified file for a specified string. To -do so, `grep` takes as its arguments a file path and a string. Then it reads +do so, `grep` takes as its arguments a file path and a string. Then, it reads the file, finds lines in that file that contain the string argument, and prints those lines. @@ -51,7 +51,7 @@ cover in detail. Let’s create a new project with, as always, `cargo new`. We’ll call our project `minigrep` to distinguish it from the `grep` tool that you might already have -on your system. +on your system: ``` $ cargo new minigrep @@ -79,13 +79,13 @@ just learning this concept, let’s implement this capability ourselves. To enable `minigrep` to read the values of command line arguments we pass to it, we’ll need the `std::env::args` function provided in Rust’s standard library. This function returns an iterator of the command line arguments passed -to `minigrep`. We’ll cover iterators fully in Chapter 13. For now, you only need to know two details about iterators: iterators +to `minigrep`. We’ll cover iterators fully in Chapter 13. For now, you only need to know two details about iterators: Iterators produce a series of values, and we can call the `collect` method on an iterator -to turn it into a collection, such as a vector, that contains all the elements +to turn it into a collection, such as a vector, which contains all the elements the iterator produces. The code in Listing 12-1 allows your `minigrep` program to read any command -line arguments passed to it, and then collect the values into a vector. +line arguments passed to it and then collect the values into a vector. src/main.rs @@ -100,8 +100,8 @@ fn main() { Listing 12-1: Collecting the command line arguments into a vector and printing them -First we bring the `std::env` module into scope with a `use` statement so we -can use its `args` function. Notice that the `std::env::args` function is +First, we bring the `std::env` module into scope with a `use` statement so that +we can use its `args` function. Notice that the `std::env::args` function is nested in two levels of modules. As we discussed in Chapter 7, in cases where the desired function is nested in more than one module, we’ve chosen to bring the parent module into @@ -164,8 +164,8 @@ chapter, we’ll ignore it and save only the two arguments we need. The program is currently able to access the values specified as command line arguments. Now we need to save the values of the two arguments in variables so -we can use the values throughout the rest of the program. We do that in Listing -12-2. +that we can use the values throughout the rest of the program. We do that in +Listing 12-2. src/main.rs @@ -214,7 +214,7 @@ capabilities instead. ## Reading a File Now we’ll add functionality to read the file specified in the `file_path` -argument. First we need a sample file to test it with: we’ll use a file with a +argument. First, we need a sample file to test it with: We’ll use a file with a small amount of text over multiple lines with some repeated words. Listing 12-3 has an Emily Dickinson poem that will work well! Create a file called *poem.txt* at the root level of your project, and enter the poem “I’m Nobody! @@ -258,15 +258,15 @@ fn main() { Listing 12-4: Reading the contents of the file specified by the second argument -First we bring in a relevant part of the standard library with a `use` -statement: we need `std::fs` to handle files. +First, we bring in a relevant part of the standard library with a `use` +statement: We need `std::fs` to handle files. In `main`, the new statement `fs::read_to_string` takes the `file_path`, opens that file, and returns a value of type `std::io::Result` that contains the file’s contents. After that, we again add a temporary `println!` statement that prints the value -of `contents` after the file is read, so we can check that the program is +of `contents` after the file is read so that we can check that the program is working so far. Let’s run this code with any string as the first command line argument (because @@ -295,7 +295,7 @@ To an admiring bog! Great! The code read and then printed the contents of the file. But the code has a few flaws. At the moment, the `main` function has multiple -responsibilities: generally, functions are clearer and easier to maintain if +responsibilities: Generally, functions are clearer and easier to maintain if each function is responsible for only one idea. The other problem is that we’re not handling errors as well as we could. The program is still small, so these flaws aren’t a big problem, but as the program grows, it will be harder to fix @@ -307,14 +307,14 @@ code. We’ll do that next. To improve our program, we’ll fix four problems that have to do with the program’s structure and how it’s handling potential errors. First, our `main` -function now performs two tasks: it parses arguments and reads files. As our +function now performs two tasks: It parses arguments and reads files. As our program grows, the number of separate tasks the `main` function handles will increase. As a function gains responsibilities, it becomes more difficult to reason about, harder to test, and harder to change without breaking one of its -parts. It’s best to separate functionality so each function is responsible for -one task. +parts. It’s best to separate functionality so that each function is responsible +for one task. -This issue also ties into the second problem: although `query` and `file_path` +This issue also ties into the second problem: Although `query` and `file_path` are configuration variables to our program, variables like `contents` are used to perform the program’s logic. The longer `main` becomes, the more variables we’ll need to bring into scope; the more variables we have in scope, the harder @@ -322,7 +322,7 @@ it will be to keep track of the purpose of each. It’s best to group the configuration variables into one structure to make their purpose clear. The third problem is that we’ve used `expect` to print an error message when -reading the file fails, but the error message just prints `Should have been able to read the file`. Reading a file can fail in a number of ways: for +reading the file fails, but the error message just prints `Should have been able to read the file`. Reading a file can fail in a number of ways: For example, the file could be missing, or we might not have permission to open it. Right now, regardless of the situation, we’d print the same error message for everything, which wouldn’t give the user any information! @@ -330,14 +330,18 @@ everything, which wouldn’t give the user any information! Fourth, we use `expect` to handle an error, and if the user runs our program without specifying enough arguments, they’ll get an `index out of bounds` error from Rust that doesn’t clearly explain the problem. It would be best if all the -error-handling code were in one place so future maintainers had only one place -to consult the code if the error-handling logic needed to change. Having all the -error-handling code in one place will also ensure that we’re printing messages -that will be meaningful to our end users. +error-handling code were in one place so that future maintainers had only one +place to consult the code if the error-handling logic needed to change. Having +all the error-handling code in one place will also ensure that we’re printing +messages that will be meaningful to our end users. Let’s address these four problems by refactoring our project. -### Separation of Concerns for Binary Projects + + + + +### Separating Concerns in Binary Projects The organizational problem of allocating responsibility for multiple tasks to the `main` function is common to many binary projects. As a result, many Rust @@ -489,10 +493,10 @@ giving up a little performance to gain simplicity is a worthwhile trade-off. > easier to start with the most efficient solution, but for now, it’s > perfectly acceptable to call `clone`. -We’ve updated `main` so it places the instance of `Config` returned by +We’ve updated `main` so that it places the instance of `Config` returned by `parse_config` into a variable named `config`, and we updated the code that -previously used the separate `query` and `file_path` variables so it now uses -the fields on the `Config` struct instead. +previously used the separate `query` and `file_path` variables so that it now +uses the fields on the `Config` struct instead. Now our code more clearly conveys that `query` and `file_path` are related and that their purpose is to configure how the program will work. Any code that @@ -508,7 +512,7 @@ relationship should be conveyed in our code. We then added a `Config` struct to name the related purpose of `query` and `file_path` and to be able to return the values’ names as struct field names from the `parse_config` function. -So now that the purpose of the `parse_config` function is to create a `Config` +So, now that the purpose of the `parse_config` function is to create a `Config` instance, we can change `parse_config` from a plain function to a function named `new` that is associated with the `Config` struct. Making this change will make the code more idiomatic. We can create instances of types in the @@ -610,9 +614,9 @@ not enough arguments note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace ``` -This output is better: we now have a reasonable error message. However, we also +This output is better: We now have a reasonable error message. However, we also have extraneous information we don’t want to give to our users. Perhaps the -technique we used in Listing 9-13 isn’t the best one to use here: a call to +technique we used in Listing 9-13 isn’t the best one to use here: A call to `panic!` is more appropriate for a programming problem than a usage problem, as discussed in Chapter 9. Instead, we’ll use the other technique you learned about in Chapter 9—returning a @@ -629,7 +633,7 @@ the successful case and will describe the problem in the error case. We’re als going to change the function name from `new` to `build` because many programmers expect `new` functions to never fail. When `Config::build` is communicating to `main`, we can use the `Result` type to signal there was a -problem. Then we can change `main` to convert an `Err` variant into a more +problem. Then, we can change `main` to convert an `Err` variant into a more practical error for our users without the surrounding text about `thread 'main'` and `RUST_BACKTRACE` that a call to `panic!` causes. Listing 12-9 shows the changes we need to make to the return value of the @@ -660,7 +664,7 @@ Our `build` function returns a `Result` with a `Config` instance in the success case and a string literal in the error case. Our error values will always be string literals that have the `'static` lifetime. -We’ve made two changes in the body of the function: instead of calling `panic!` +We’ve made two changes in the body of the function: Instead of calling `panic!` when the user doesn’t pass enough arguments, we now return an `Err` value, and we’ve wrapped the `Config` return value in an `Ok`. These changes make the function conform to its new type signature. @@ -704,8 +708,8 @@ In this listing, we’ve used a method we haven’t covered in detail yet: `unwrap_or_else`, which is defined on `Result` by the standard library. Using `unwrap_or_else` allows us to define some custom, non-`panic!` error handling. If the `Result` is an `Ok` value, this method’s behavior is similar -to `unwrap`: it returns the inner value that `Ok` is wrapping. However, if the -value is an `Err` value, this method calls the code in the *closure*, which is +to `unwrap`: It returns the inner value that `Ok` is wrapping. However, if the +value is an `Err` value, this method calls the code in the closure, which is an anonymous function we define and pass as an argument to `unwrap_or_else`. We’ll cover closures in more detail in Chapter 13. For now, you just need to know that `unwrap_or_else` will pass the inner value of @@ -716,7 +720,7 @@ appears between the vertical pipes. The code in the closure can then use the We’ve added a new `use` line to bring `process` from the standard library into scope. The code in the closure that will be run in the error case is only two -lines: we print the `err` value and then call `process::exit`. The +lines: We print the `err` value and then call `process::exit`. The `process::exit` function will stop the program immediately and return the number that was passed as the exit status code. This is similar to the `panic!`-based handling we used in Listing 12-8, but we no longer get all the @@ -734,12 +738,12 @@ Great! This output is much friendlier for our users. - + -### Extracting Logic from the main Function +### Extracting Logic from main Now that we’ve finished refactoring the configuration parsing, let’s turn to -the program’s logic. As we stated in “Separation of Concerns for Binary +the program’s logic. As we stated in “Separating Concerns in Binary Projects”, we’ll extract a function named `run` that will hold all the logic currently in the `main` function that isn’t involved with setting up configuration or handling @@ -777,7 +781,11 @@ The `run` function now contains all the remaining logic from `main`, starting from reading the file. The `run` function takes the `Config` instance as an argument. -#### Returning Errors from the run Function + + + + +#### Returning Errors from run With the remaining program logic separated into the `run` function, we can improve the error handling, as we did with `Config::build` in Listing 12-9. @@ -810,14 +818,14 @@ the `run` function to `Result<(), Box>`. This function previously returned the unit type, `()`, and we keep that as the value returned in the `Ok` case. -For the error type, we used the *trait object* `Box` (and we’ve -brought `std::error::Error` into scope with a `use` statement at the top). -We’ll cover trait objects in Chapter 18. For now, just -know that `Box` means the function will return a type that -implements the `Error` trait, but we don’t have to specify what particular type -the return value will be. This gives us flexibility to return error values that -may be of different types in different error cases. The `dyn` keyword is short -for *dynamic*. +For the error type, we used the trait object `Box` (and we brought +`std::error::Error` into scope with a `use` statement at the top). We’ll cover +trait objects in Chapter 18. For now, just know that +`Box` means the function will return a type that implements the +`Error` trait, but we don’t have to specify what particular type the return +value will be. This gives us flexibility to return error values that may be of +different types in different error cases. The `dyn` keyword is short for +*dynamic*. Second, we’ve removed the call to `expect` in favor of the `?` operator, as we talked about in Chapter 9. Rather than @@ -827,7 +835,7 @@ for the caller to handle. Third, the `run` function now returns an `Ok` value in the success case. We’ve declared the `run` function’s success type as `()` in the signature, which means we need to wrap the unit type value in the `Ok` value. This -`Ok(())` syntax might look a bit strange at first, but using `()` like this is +`Ok(())` syntax might look a bit strange at first. But using `()` like this is the idiomatic way to indicate that we’re calling `run` for its side effects only; it doesn’t return a value we need. @@ -901,7 +909,7 @@ the success case, we only care about detecting an error, so we don’t need `unwrap_or_else` to return the unwrapped value, which would only be `()`. The bodies of the `if let` and the `unwrap_or_else` functions are the same in -both cases: we print the error and exit. +both cases: We print the error and exit. ### Splitting Code into a Library Crate @@ -921,9 +929,12 @@ the signature in more detail when we fill in the implementation. src/lib.rs ``` +pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> { + unimplemented!(); +} ``` -Listing 12-13: Defining the `search` function in *src/lib.rs* +Listing 12-13: Defining the `search` function in *src/lib.rs* We’ve used the `pub` keyword on the function definition to designate `search` as part of our library crate’s public API. We now have a library crate that we @@ -943,6 +954,7 @@ fn main() { } // --snip-- + fn run(config: Config) -> Result<(), Box> { let contents = fs::read_to_string(config.file_path)?; @@ -959,7 +971,7 @@ Listing 12-14: Using the `minigrep` library crate’s `search` function in *src/ We add a `use minigrep::search` line to bring the `search` function from the library crate into the binary crate’s scope. Then, in the `run` function, rather than printing out the contents of the file, we call the `search` -function and pass the `config.query` value and `contents` as arguments. Then +function and pass the `config.query` value and `contents` as arguments. Then, `run` will use a `for` loop to print each line returned from `search` that matched the query. This is also a good time to remove the `println!` calls in the `main` function that displayed the query and the file path so that our @@ -967,7 +979,7 @@ program only prints the search results (if no errors occur). Note that the search function will be collecting all the results into a vector it returns before any printing happens. This implementation could be slow to -display results when searching large files because results aren’t printed as +display results when searching large files, because results aren’t printed as they’re found; we’ll discuss a possible way to fix this using iterators in Chapter 13. @@ -976,10 +988,14 @@ future. Now it’s much easier to handle errors, and we’ve made the code more modular. Almost all of our work will be done in *src/lib.rs* from here on out. Let’s take advantage of this newfound modularity by doing something that would -have been difficult with the old code but is easy with the new code: we’ll +have been difficult with the old code but is easy with the new code: We’ll write some tests! -## Developing the Library’s Functionality with Test-Driven Development + + + + +## Adding Functionality with Test-Driven Development Now that we have the search logic in *src/lib.rs* separate from the `main` function, it’s much easier to write tests for the core functionality of our @@ -998,7 +1014,7 @@ the test-driven development (TDD) process with the following steps: Though it’s just one of many ways to write software, TDD can help drive code design. Writing the test before you write the code that makes the test pass -helps to maintain high test coverage throughout the process. +helps maintain high test coverage throughout the process. We’ll test-drive the implementation of the functionality that will actually do the searching for the query string in the file contents and produce a list of @@ -1009,7 +1025,7 @@ lines that match the query. We’ll add this functionality in a function called In *src/lib.rs*, we’ll add a `tests` module with a test function, as we did in Chapter 11. The test function specifies the -behavior we want the `search` function to have: it will take a query and the +behavior we want the `search` function to have: It will take a query and the text to search, and it will return only the lines from the text that contain the query. Listing 12-15 shows this test. @@ -1047,8 +1063,8 @@ If we run this test, it will currently fail because the `unimplemented!` macro panics with the message “not implemented”. In accordance with TDD principles, we’ll take a small step of adding just enough code to get the test to not panic when calling the function by defining the `search` function to always return an -empty vector, as shown in Listing 12-16. Then the test should compile and fail -because an empty vector doesn’t match a vector containing the line `"safe, fast, productive."` +empty vector, as shown in Listing 12-16. Then, the test should compile and fail +because an empty vector doesn’t match a vector containing the line `"safe, fast, productive."`. src/lib.rs @@ -1058,7 +1074,7 @@ pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> { } ``` -Listing 12-16: Defining just enough of the `search` function so calling it won’t panic +Listing 12-16: Defining just enough of the `search` function so that calling it won’t panic Now let’s discuss why we need to define an explicit lifetime `'a` in the signature of `search` and use that lifetime with the `contents` argument and @@ -1143,7 +1159,7 @@ pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> { Listing 12-17: Iterating through each line in `contents` The `lines` method returns an iterator. We’ll talk about iterators in depth in -Chapter 13, but recall that you saw this way +Chapter 13. But recall that you saw this way of using an iterator in Listing 3-5, where we used a `for` loop with an iterator to run some code on each item in a collection. @@ -1195,7 +1211,7 @@ pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> { } ``` -Listing 12-19: Storing the lines that match so we can return them +Listing 12-19: Storing the lines that match so that we can return them Now the `search` function should return only the lines that contain `query`, and our test should pass. Let’s run the test: @@ -1284,7 +1300,11 @@ users enter it each time they want it to apply, but by instead making it an environment variable, we allow our users to set the environment variable once and have all their searches be case insensitive in that terminal session. -### Writing a Failing Test for the Case-Insensitive search Function + + + + +### Writing a Failing Test for Case-Insensitive Search We first add a new `search_case_insensitive` function to the `minigrep` library that will be called when the environment variable has a value. We’ll continue @@ -1376,10 +1396,10 @@ pub fn search_case_insensitive<'a>( Listing 12-21: Defining the `search_case_insensitive` function to lowercase the query and the line before comparing them -First we lowercase the `query` string and store it in a new variable with the +First, we lowercase the `query` string and store it in a new variable with the same name, shadowing the original `query`. Calling `to_lowercase` on the query is necessary so that no matter whether the user’s query is `"rust"`, `"RUST"`, -`"Rust"`, or ```"``rUsT``"```, we’ll treat the query as if it were `"rust"` and be +`"Rust"`, or `"rUsT"`, we’ll treat the query as if it were `"rust"` and be insensitive to the case. While `to_lowercase` will handle basic Unicode, it won’t be 100 percent accurate. If we were writing a real application, we’d want to do a bit more work here, but this section is about environment variables, @@ -1387,7 +1407,7 @@ not Unicode, so we’ll leave it at that here. Note that `query` is now a `String` rather than a string slice because calling `to_lowercase` creates new data rather than referencing existing data. Say the -query is `"rUsT"`, as an example: that string slice doesn’t contain a lowercase +query is `"rUsT"`, as an example: That string slice doesn’t contain a lowercase `u` or `t` for us to use, so we have to allocate a new `String` containing `"rust"`. When we pass `query` as an argument to the `contains` method now, we need to add an ampersand because the signature of `contains` is defined to take @@ -1425,8 +1445,8 @@ test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; fini ``` -Great! They passed. Now, let’s call the new `search_case_insensitive` function -from the `run` function. First we’ll add a configuration option to the `Config` +Great! They passed. Now let’s call the new `search_case_insensitive` function +from the `run` function. First, we’ll add a configuration option to the `Config` struct to switch between case-sensitive and case-insensitive search. Adding this field will cause compiler errors because we aren’t initializing this field anywhere yet: @@ -1518,11 +1538,11 @@ care about the *value* of the environment variable, just whether it’s set or unset, so we’re checking `is_ok` rather than using `unwrap`, `expect`, or any of the other methods we’ve seen on `Result`. -We pass the value in the `ignore_case` variable to the `Config` instance so the -`run` function can read that value and decide whether to call +We pass the value in the `ignore_case` variable to the `Config` instance so +that the `run` function can read that value and decide whether to call `search_case_insensitive` or `search`, as we implemented in Listing 12-22. -Let’s give it a try! First we’ll run our program without the environment +Let’s give it a try! First, we’ll run our program without the environment variable set and with the query `to`, which should match any line that contains the word *to* in all lowercase: @@ -1536,7 +1556,7 @@ How dreary to be somebody! ``` Looks like that still works! Now let’s run the program with `IGNORE_CASE` set -to `1` but with the same query *to*: +to `1` but with the same query `to`: ``` $ IGNORE_CASE=1 cargo run -- to poem.txt @@ -1585,9 +1605,13 @@ precedence if the program is run with one set to case sensitive and one set to ignore case. The `std::env` module contains many more useful features for dealing with -environment variables: check out its documentation to see what is available. +environment variables: Check out its documentation to see what is available. + + + + -## Writing Error Messages to Standard Error Instead of Standard Output +## Redirecting Errors to Standard Error At the moment, we’re writing all of our output to the terminal using the `println!` macro. In most terminals, there are two kinds of output: *standard @@ -1601,7 +1625,7 @@ to use something else to print to standard error. ### Checking Where Errors Are Written -First let’s observe how the content printed by `minigrep` is currently being +First, let’s observe how the content printed by `minigrep` is currently being written to standard output, including any error messages we want to write to standard error instead. We’ll do that by redirecting the standard output stream to a file while intentionally causing an error. We won’t redirect the standard @@ -1609,9 +1633,10 @@ error stream, so any content sent to standard error will continue to display on the screen. Command line programs are expected to send error messages to the standard error -stream so we can still see error messages on the screen even if we redirect the -standard output stream to a file. Our program is not currently well behaved: -we’re about to see that it saves the error message output to a file instead! +stream so that we can still see error messages on the screen even if we +redirect the standard output stream to a file. Our program is not currently +well behaved: We’re about to see that it saves the error message output to a +file instead! To demonstrate this behavior, we’ll run the program with `>` and the file path, *output.txt*, that we want to redirect the standard output stream to. We won’t @@ -1631,8 +1656,8 @@ Problem parsing arguments: not enough arguments ``` Yup, our error message is being printed to standard output. It’s much more -useful for error messages like this to be printed to standard error so only -data from a successful run ends up in the file. We’ll change that. +useful for error messages like this to be printed to standard error so that +only data from a successful run ends up in the file. We’ll change that. ### Printing Errors to Standard Error diff --git a/nostarch/chapter13.md b/nostarch/chapter13.md index 73fede40be..e22c6baa14 100644 --- a/nostarch/chapter13.md +++ b/nostarch/chapter13.md @@ -23,19 +23,20 @@ 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 closures and iterators to improve the I/O project in Chapter 12 -* The performance of closures and iterators (spoiler alert: they’re faster than +* The performance of closures and iterators (spoiler alert: They’re faster than you might think!) We’ve already covered some other Rust features, such as pattern matching and enums, that are also influenced by the functional style. Because mastering -closures and iterators is an important part of writing idiomatic, fast Rust +closures and iterators is an important part of writing fast, idiomatic, Rust code, we’ll devote this entire chapter to them. - + + -## Closures: Anonymous Functions That Capture Their Environment +## Closures 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 @@ -49,11 +50,12 @@ customization. + -### Capturing the Environment with Closures +### Capturing the Environment We’ll first examine how we can use closures to capture values from the -environment they’re defined in for later use. Here’s the scenario: every so +environment they’re defined in for later use. Here’s the scenario: Every so often, our T-shirt company gives away an exclusive, limited-edition shirt to someone on our mailing list as a promotion. People on the mailing list can optionally add their favorite color to their profile. If the person chosen for @@ -66,8 +68,8 @@ enum called `ShirtColor` that has the variants `Red` and `Blue` (limiting the number of colors available for simplicity). We represent the company’s inventory with an `Inventory` struct that has a field named `shirts` that contains a `Vec` representing the shirt colors currently in stock. -The method `giveaway` defined on `Inventory` gets the optional shirt -color preference of the free-shirt winner, and returns the shirt color the +The method `giveaway` defined on `Inventory` gets the optional shirt color +preference of the free-shirt winner, and it returns the shirt color the person will get. This setup is shown in Listing 13-1. src/main.rs @@ -172,7 +174,11 @@ immutable reference to the `self` `Inventory` instance and passes it with the code we specify to the `unwrap_or_else` method. Functions, on the other hand, are not able to capture their environment in this way. -### Closure Type Inference and Annotation + + + + +### Inferring and Annotating Closure Types 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 @@ -180,8 +186,8 @@ like `fn` functions do. Type annotations are required on functions because the types are 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. Closures, on the other hand, 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. +in an exposed interface like this: They’re stored in variables, and they’re +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 @@ -368,10 +374,10 @@ 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 +the `borrows_mutably` closure: When `borrows_mutably` is defined, it captures a mutable reference to `list`. We don’t use the closure again after the closure is called, so the mutable borrow ends. Between the closure definition and the -closure call, an immutable borrow to print isn’t allowed because no other +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! @@ -415,17 +421,18 @@ main thread finishes, or the main thread might finish first. If the main thread maintained ownership of `list` but ended before the new thread and drops `list`, the immutable reference in the thread would be invalid. Therefore, the compiler requires that `list` be moved into the closure given to the new thread -so the reference will be valid. Try removing the `move` keyword or using `list` -in the main thread after the closure is defined to see what compiler errors you -get! +so that the reference will be valid. Try removing the `move` keyword or using +`list` in the main thread after the closure is defined to see what compiler +errors you get! + -### Moving Captured Values Out of Closures and the Fn Traits +### Moving Captured Values Out of Closures Once a closure has captured a reference or captured ownership of a value from the environment where the closure is defined (thus affecting what, if anything, @@ -433,7 +440,7 @@ is moved *into* the closure), the code in the body of the closure defines what happens to the references or values when the closure is evaluated later (thus affecting what, if anything, is moved *out of* the closure). -A closure body can do any of the following: move a captured value out of the +A closure body can do any of the following: Move a captured value out of the closure, mutate the captured value, neither move nor mutate the value, or capture nothing from the environment to begin with. @@ -447,14 +454,13 @@ depending on how the closure’s body handles the values: at least this trait because all closures can be called. A closure that moves captured values out of its body will only implement `FnOnce` and none of the other `Fn` traits because it can only be called once. -* `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. +* `FnMut` applies to closures that don’t move captured values out of their body + but might mutate the captured values. These closures can be called more than + once. * `Fn` applies to closures that don’t move captured values out of their body - and that don’t mutate captured values, as well as closures that capture - nothing from their environment. 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. + and don’t mutate captured values, as well as closures that capture nothing + from their environment. 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. Let’s look at the definition of the `unwrap_or_else` method on `Option` that we used in Listing 13-1: @@ -475,7 +481,7 @@ impl Option { 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 +`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 the additional generic type @@ -485,7 +491,7 @@ 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 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` will not call `f` more than once. 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 all three kinds of @@ -503,9 +509,9 @@ Now let’s look at the standard library method `sort_by_key`, defined on slices to see how that differs from `unwrap_or_else` and why `sort_by_key` uses `FnMut` instead of `FnOnce` for the trait bound. The closure gets one argument in the form of 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 +and it 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-7, we have a list of `Rectangle` instances and we use `sort_by_key` +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. src/main.rs @@ -591,13 +597,13 @@ 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 +This is a contrived, convoluted way (that doesn’t work) to try to count the number of times `sort_by_key` calls the closure 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` and 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 @@ -632,7 +638,7 @@ move values out of the environment. Keeping a counter in the environment and incrementing its value in the closure body is a more straightforward way to count the number of times the closure is called. 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: +`num_sort_operations` counter and can therefore be called more than once. src/main.rs @@ -659,7 +665,7 @@ fn main() { } ``` -Listing 13-9: Using an `FnMut` closure with `sort_by_key` is allowed +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. In the next section, we’ll discuss iterators. Many @@ -774,11 +780,11 @@ src/lib.rs 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 +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 +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 @@ -797,7 +803,7 @@ 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 adapters*, because calling them +Methods that call `next` are called *consuming adapters* 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 @@ -821,7 +827,7 @@ src/lib.rs 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 +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 @@ -870,7 +876,7 @@ warning: `iterators` (bin "iterators") generated 1 warning ``` The code in Listing 13-14 doesn’t do anything; the closure we’ve specified -never gets called. The warning reminds us why: iterator adapters are lazy, and +never gets called. The warning reminds us why: Iterator adapters are lazy, and we need to consume the iterator here. To fix this warning and consume the iterator, we’ll use the `collect` method, @@ -902,7 +908,11 @@ You can chain multiple calls to iterator adapters to perform complex actions in a readable way. But because all iterators are lazy, you have to call one of the consuming adapter methods to get results from calls to iterator adapters. -### Using Closures That Capture Their Environment + + + + +### Closures That Capture Their Environment Many iterator adapters take closures as arguments, and commonly the closures we’ll specify as arguments to iterator adapters will be closures that capture @@ -976,18 +986,18 @@ 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`. +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. +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 @@ -1035,7 +1045,8 @@ 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 `build` function doesn’t own `args`. To return ownership of a `Config` instance, we had to clone the values from the `query` -and `file_path` fields of `Config` so the `Config` instance can own its values. +and `file_path` fields of `Config` so that the `Config` instance can own its +values. With our new knowledge about iterators, we can change the `build` function to take ownership of an iterator as its argument instead of borrowing a slice. @@ -1110,18 +1121,21 @@ 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::build` 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` trait and -returns `String` items. +We’ve updated the signature of the `Config::build` function so that 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 “Using Traits as Parameters” +section of Chapter 10 means that `args` can be any type that implements the +`Iterator` trait 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 + + + + +#### Using Iterator Trait Methods Next, we’ll fix the body of `Config::build`. Because `args` implements the `Iterator` trait, we know we can call the `next` method on it! Listing 13-20 @@ -1161,13 +1175,17 @@ Listing 13-20: Changing the body of `Config::build` 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. Then we call `next` to get the -value we want to put in the `query` field of `Config`. If `next` returns `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 `file_path` value. +`next` and do nothing with the return value. Then, we call `next` to get the +value we want to put in the `query` field of `Config`. If `next` returns +`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 `file_path` value. + + + + -### Making Code Clearer with Iterator Adapters +### Clarifying Code with Iterator Adapters We can also take advantage of iterators in the `search` function in our I/O project, which is reproduced here in Listing 13-21 as it was in Listing 12-19. @@ -1226,7 +1244,7 @@ until it has collected all of the results, but after the change, the results will be printed as each matching line is found because the `for` loop in the `run` function is able to take advantage of the laziness of the iterator. - + @@ -1240,14 +1258,18 @@ 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 adapters 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. +objective of the loop. This abstracts away some of the commonplace code so that +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 lower-level loop will be faster. Let’s talk about performance. -## Comparing Performance: Loops vs. Iterators + + + + +## Performance in 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 @@ -1271,12 +1293,12 @@ 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 +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 that 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): +zero-overhead in his 2012 ETAPS keynote presentation “Foundations of C++”: > 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 diff --git a/nostarch/chapter14.md b/nostarch/chapter14.md index fb2491d04f..e376fe0109 100644 --- a/nostarch/chapter14.md +++ b/nostarch/chapter14.md @@ -12,18 +12,18 @@ So far, we’ve used only the most basic features of Cargo to build, run, and test our code, but it can do a lot more. In this chapter, we’ll discuss some of its other, more advanced features to show you how to do the following: -* Customize your build through release profiles -* Publish libraries on crates.io -* Organize large projects with workspaces -* Install binaries from crates.io -* Extend Cargo using custom commands +* Customize your build through release profiles. +* Publish libraries on crates.io. +* Organize large projects with workspaces. +* Install binaries from crates.io. +* Extend Cargo using custom commands. Cargo can do even more than the functionality we cover in this chapter, so for a full explanation of all its features, see its documentation at *https://doc.rust-lang.org/cargo/*. ## Customizing Builds with Release Profiles -In Rust, *release profiles* are predefined and customizable profiles with +In Rust, *release profiles* are predefined, customizable profiles with different configurations that allow a programmer to have more control over various options for compiling code. Each profile is configured independently of the others. @@ -158,7 +158,7 @@ rendered, as shown in Figure 14-1. Rendered HTML documentation for the `add_one` function of `my_crate` -Figure 14-1: HTML documentation for the `add_one` +Figure 14-1: The HTML documentation for the `add_one` function #### Commonly Used Sections @@ -167,12 +167,12 @@ We used the `# Examples` Markdown heading in Listing 14-1 to create a section in the HTML with the title “Examples.” Here are some other sections that crate authors commonly use in their documentation: -* **Panics**: The scenarios in which the function being documented could - panic. Callers of the function who don’t want their programs to panic should - make sure they don’t call the function in these situations. +* **Panics**: These are the scenarios in which the function being documented + could panic. Callers of the function who don’t want their programs to panic + should make sure they don’t call the function in these situations. * **Errors**: If the function returns a `Result`, describing the kinds of errors that might occur and what conditions might cause those errors to be - returned can be helpful to callers so they can write code to handle the + returned can be helpful to callers so that they can write code to handle the different kinds of errors in different ways. * **Safety**: If the function is `unsafe` to call (we discuss unsafety in Chapter 20), there should be a section explaining why the function is unsafe @@ -185,12 +185,12 @@ interested in knowing about. #### Documentation Comments as Tests Adding example code blocks in your documentation comments can help demonstrate -how to use your library, and doing so has an additional bonus: running `cargo test` will run the code examples in your documentation as tests! Nothing is -better than documentation with examples. But nothing is worse than examples -that don’t work because the code has changed since the documentation was -written. If we run `cargo test` with the documentation for the `add_one` -function from Listing 14-1, we will see a section in the test results that looks -like this: +how to use your library and has an additional bonus: Running `cargo test` will +run the code examples in your documentation as tests! Nothing is better than +documentation with examples. But nothing is worse than examples that don’t work +because the code has changed since the documentation was written. If we run +`cargo test` with the documentation for the `add_one` function from Listing +14-1, we will see a section in the test results that looks like this: + + + +#### Contained Item Comments The style of doc comment `//!` adds documentation to the item that *contains* -the comments rather than to the items *following* the comments. We typically use -these doc comments inside the crate root file (*src/lib.rs* by convention) or -inside a module to document the crate or the module as a whole. +the comments rather than to the items *following* the comments. We typically +use these doc comments inside the crate root file (*src/lib.rs* by convention) +or inside a module to document the crate or the module as a whole. For example, to add documentation that describes the purpose of the `my_crate` crate that contains the `add_one` function, we add documentation comments that @@ -235,7 +239,7 @@ src/lib.rs // --snip-- ``` -Listing 14-2: Documentation for the `my_crate` crate as a whole +Listing 14-2: The documentation for the `my_crate` crate as a whole Notice there isn’t any code after the last line that begins with `//!`. Because we started the comments with `//!` instead of `///`, we’re documenting the item @@ -243,20 +247,24 @@ that contains this comment rather than an item that follows this comment. In this case, that item is the *src/lib.rs* file, which is the crate root. These comments describe the entire crate. -When we run `cargo doc --open`, these comments will display on the front -page of the documentation for `my_crate` above the list of public items in the +When we run `cargo doc --open`, these comments will display on the front page +of the documentation for `my_crate` above the list of public items in the crate, as shown in Figure 14-2. +Documentation comments within items are useful for describing crates and +modules especially. Use them to explain the overall purpose of the container to +help your users understand the crate’s organization. + Rendered HTML documentation with a comment for the crate as a whole -Figure 14-2: Rendered documentation for `my_crate`, +Figure 14-2: The rendered documentation for `my_crate`, including the comment describing the crate as a whole -Documentation comments within items are useful for describing crates and -modules especially. Use them to explain the overall purpose of the container to -help your users understand the crate’s organization. + -### Exporting a Convenient Public API with pub use + + +### Exporting a Convenient Public API The structure of your public API is a major consideration when publishing a crate. People who use your crate are less familiar with the structure than you @@ -273,7 +281,7 @@ exists. They might also be annoyed at having to enter `use my_crate::some_module The good news is that if the structure *isn’t* convenient for others to use from another library, you don’t have to rearrange your internal organization: -instead, you can re-export items to make a public structure that’s different +Instead, you can re-export items to make a public structure that’s different from your private structure by using `pub use`. *Re-exporting* takes a public item in one location and makes it public in another location, as if it were defined in the other location instead. @@ -324,7 +332,7 @@ generated by `cargo doc` would look like. Rendered documentation for the `art` crate that lists the `kinds` and `utils` modules -Figure 14-3: Front page of the documentation for `art` +Figure 14-3: The front page of the documentation for `art` that lists the `kinds` and `utils` modules Note that the `PrimaryColor` and `SecondaryColor` types aren’t listed on the @@ -418,11 +426,12 @@ people who use the crate. Another common use of `pub use` is to re-export definitions of a dependency in the current crate to make that crate’s definitions part of your crate’s public API. -Creating a useful public API structure is more of an art than a science, and -you can iterate to find the API that works best for your users. Choosing `pub use` gives you flexibility in how you structure your crate internally and -decouples that internal structure from what you present to your users. Look at -some of the code of crates you’ve installed to see if their internal structure -differs from their public API. +Creating a useful public API structure is more an art than a science, and you +can iterate to find the API that works best for your users. Choosing `pub use` +gives you flexibility in how you structure your crate internally and decouples +that internal structure from what you present to your users. Look at some of +the code of crates you’ve installed to see if their internal structure differs +from their public API. ### Setting Up a Crates.io Account @@ -433,7 +442,7 @@ in via a GitHub account. (The GitHub account is currently a requirement, but the site might support other ways of creating an account in the future.) Once you’re logged in, visit your account settings at https://crates.io/me/ and retrieve your -API key. Then run the `cargo login` command and paste your API key when prompted, like this: +API key. Then, run the `cargo login` command and paste your API key when prompted, like this: ``` $ cargo login @@ -441,7 +450,7 @@ abcdefghijklmnopqrstuvwxyz012345 ``` This command will inform Cargo of your API token and store it locally in -*~/.cargo/credentials.toml*. Note that this token is a *secret*: do not share +*~/.cargo/credentials.toml*. Note that this token is a secret: Do not share it with anyone else. If you do share it with anyone for any reason, you should revoke it and generate a new token on crates.io. @@ -489,14 +498,14 @@ Caused by: the remote server responded with an error (status 400 Bad Request): missing or empty metadata fields: description, license. Please see https://doc.rust-lang.org/cargo/reference/manifest.html for more information on configuring these fields ``` -This results in an error because you’re missing some crucial information: a -description and license are required so people will know what your crate does -and under what terms they can use it. In *Cargo.toml*, add a description that’s -just a sentence or two, because it will appear with your crate in search -results. For the `license` field, you need to give a *license identifier value*. -The Linux Foundation’s Software Package Data Exchange (SPDX) at *https://spdx.org/licenses/* lists the -identifiers you can use for this value. For example, to specify that you’ve -licensed your crate using the MIT License, add the `MIT` identifier: +This results in an error because you’re missing some crucial information: A +description and license are required so that people will know what your crate +does and under what terms they can use it. In *Cargo.toml*, add a description +that’s just a sentence or two, because it will appear with your crate in search +results. For the `license` field, you need to give a *license identifier +value*. The Linux Foundation’s Software Package Data Exchange (SPDX) at *https://spdx.org/licenses/* +lists the identifiers you can use for this value. For example, to specify that +you’ve licensed your crate using the MIT License, add the `MIT` identifier: Filename: Cargo.toml @@ -586,13 +595,14 @@ When you’ve made changes to your crate and are ready to release a new version, you change the `version` value specified in your *Cargo.toml* file and republish. Use the Semantic Versioning rules at *https://semver.org/* to decide what an appropriate next version number is, based on the kinds of changes you’ve made. -Then run `cargo publish` to upload the new version. +Then, run `cargo publish` to upload the new version. - + + -### Deprecating Versions from Crates.io with cargo yank +### Deprecating Versions from Crates.io Although you can’t remove previous versions of a crate, you can prevent any future projects from adding them as a new dependency. This is useful when a @@ -607,8 +617,8 @@ yank means that all projects with a *Cargo.lock* will not break, and any future To yank a version of a crate, in the directory of the crate that you’ve previously published, run `cargo yank` and specify which version you want to yank. For example, if we’ve published a crate named `guessing_game` version -1.0.1 and we want to yank it, in the project directory for `guessing_game` we’d -run: +1.0.1 and we want to yank it, then we’d run the following in the project +directory for `guessing_game`: + + + +### Depending on an External Package Notice that the workspace has only one *Cargo.lock* file at the top level, rather than having a *Cargo.lock* in each crate’s directory. This ensures that @@ -853,7 +867,7 @@ resolve both of those to one version of `rand` and record that in the one *Cargo.lock*. Making all crates in the workspace use the same dependencies means the crates will always be compatible with each other. Let’s add the `rand` crate to the `[dependencies]` section in the *add_one/Cargo.toml* file -so we can use the `rand` crate in the `add_one` crate: +so that we can use the `rand` crate in the `add_one` crate: + @@ -1057,14 +1071,14 @@ packages that have binary targets. A *binary target* is the runnable program that is created if the crate has a *src/main.rs* file or another file specified as a binary, as opposed to a library target that isn’t runnable on its own but is suitable for including within other programs. Usually, crates have -information in the *README* file about whether a crate is a library, has a +information in the README file about whether a crate is a library, has a binary target, or both. All binaries installed with `cargo install` are stored in the installation root’s *bin* folder. If you installed Rust using *rustup.rs* and don’t have any custom configurations, this directory will be *$HOME/.cargo/bin*. Ensure that -directory is in your `$PATH` to be able to run programs you’ve installed with -`cargo install`. +this directory is in your `$PATH` to be able to run programs you’ve installed +with `cargo install`. For example, in Chapter 12 we mentioned that there’s a Rust implementation of the `grep` tool called `ripgrep` for searching files. To install `ripgrep`, we @@ -1094,9 +1108,9 @@ then run `rg --help` and start using a faster, Rustier tool for searching files! ## Extending Cargo with Custom Commands -Cargo is designed so you can extend it with new subcommands without having to -modify it. If a binary in your `$PATH` is named `cargo-something`, you can run -it as if it were a Cargo subcommand by running `cargo something`. Custom +Cargo is designed so that you can extend it with new subcommands without having +to modify it. If a binary in your `$PATH` is named `cargo-something`, you can +run it as if it were a Cargo subcommand by running `cargo something`. Custom commands like this are also listed when you run `cargo --list`. Being able to use `cargo install` to install extensions and then run them just like the built-in Cargo tools is a super-convenient benefit of Cargo’s design! diff --git a/nostarch/chapter15.md b/nostarch/chapter15.md index 4403925cb2..a3e5c900a4 100644 --- a/nostarch/chapter15.md +++ b/nostarch/chapter15.md @@ -8,7 +8,7 @@ directory, so all fixes need to be made in `/src/`. # Smart Pointers -A *pointer* is a general concept for a variable that contains an address in +A pointer is a general concept for a variable that contains an address in memory. This address refers to, or “points at,” some other data. The most common kind of pointer in Rust is a reference, which you learned about in Chapter 4. References are indicated by the `&` symbol and borrow the value they @@ -17,7 +17,7 @@ data, and they have no overhead. *Smart pointers*, on the other hand, are data structures that act like a pointer but also have additional metadata and capabilities. The concept of -smart pointers isn’t unique to Rust: smart pointers originated in C++ and exist +smart pointers isn’t unique to Rust: Smart pointers originated in C++ and exist in other languages as well. Rust has a variety of smart pointers defined in the standard library that provide functionality beyond that provided by references. To explore the general concept, we’ll look at a couple of different examples of @@ -25,17 +25,17 @@ smart pointers, including a *reference counting* smart pointer type. This pointer enables you to allow data to have multiple owners by keeping track of the number of owners and, when no owners remain, cleaning up the data. -Rust, with its concept of ownership and borrowing, has an additional difference -between references and smart pointers: while references only borrow data, in -many cases smart pointers *own* the data they point to. +In Rust, with its concept of ownership and borrowing, there is an additional +difference between references and smart pointers: While references only borrow +data, in many cases smart pointers *own* the data they point to. Smart pointers are usually implemented using structs. Unlike an ordinary struct, smart pointers implement the `Deref` and `Drop` traits. The `Deref` trait allows an instance of the smart pointer struct to behave like a reference -so you can write your code to work with either references or smart pointers. -The `Drop` trait allows you to customize the code that’s run when an instance -of the smart pointer goes out of scope. In this chapter, we’ll discuss both of -these traits and demonstrate why they’re important to smart pointers. +so that you can write your code to work with either references or smart +pointers. The `Drop` trait allows you to customize the code that’s run when an +instance of the smart pointer goes out of scope. In this chapter, we’ll discuss +both of these traits and demonstrate why they’re important to smart pointers. Given that the smart pointer pattern is a general design pattern used frequently in Rust, this chapter won’t cover every existing smart pointer. Many @@ -64,11 +64,11 @@ Boxes don’t have performance overhead, other than storing their data on the heap instead of on the stack. But they don’t have many extra capabilities either. You’ll use them most often in these situations: -* When you have a type whose size can’t be known at compile time and you want +* When you have a type whose size can’t be known at compile time, and you want to use a value of that type in a context that requires an exact size -* When you have a large amount of data and you want to transfer ownership but - ensure the data won’t be copied when you do so -* When you want to own a value and you care only that it’s a type that +* When you have a large amount of data, and you want to transfer ownership but + ensure that the data won’t be copied when you do so +* When you want to own a value, and you care only that it’s a type that implements a particular trait rather than being of a specific type We’ll demonstrate the first situation in “Enabling Recursive Types with @@ -78,11 +78,15 @@ because the data is copied around on the stack. To improve performance in this situation, we can store the large amount of data on the heap in a box. Then, only the small amount of pointer data is copied around on the stack, while the data it references stays in one place on the heap. The third case is known as a -*trait object*, and “Using Trait Objects That Allow for Values of Different -Types,” in Chapter 18 is devoted to that topic. -So what you learn here you’ll apply again in that section! +*trait object*, and “Using Trait Objects to Abstract over Shared +Behavior” in Chapter 18 is devoted to that +topic. So, what you learn here you’ll apply again in that section! -### Using Box to Store Data on the Heap + + + + +### Storing Data on the Heap Before we discuss the heap storage use case for `Box`, we’ll cover the syntax and how to interact with values stored within a `Box`. @@ -123,13 +127,17 @@ types could theoretically continue infinitely, so Rust can’t know how much spa the value needs. Because boxes have a known size, we can enable recursive types by inserting a box in the recursive type definition. -As an example of a recursive type, let’s explore the *cons list*. This is a data +As an example of a recursive type, let’s explore the cons list. This is a data type commonly found in functional programming languages. The cons list type we’ll define is straightforward except for the recursion; therefore, the -concepts in the example we’ll work with will be useful any time you get into +concepts in the example we’ll work with will be useful anytime you get into more complex situations involving recursive types. -#### More Information About the Cons List + + + + +#### Understanding the Cons List A *cons list* is a data structure that comes from the Lisp programming language and its dialects, is made up of nested pairs, and is the Lisp version of a @@ -146,11 +154,11 @@ list `1, 2, 3` with each pair in parentheses: ``` Each item in a cons list contains two elements: the value of the current item -and the next item. The last item in the list contains only a value called `Nil` -without a next item. A cons list is produced by recursively calling the `cons` -function. The canonical name to denote the base case of the recursion is `Nil`. -Note that this is not the same as the “null” or “nil” concept discussed in -Chapter 6, which is an invalid or absent value. +and of the next item. The last item in the list contains only a value called +`Nil` without a next item. A cons list is produced by recursively calling the +`cons` function. The canonical name to denote the base case of the recursion is +`Nil`. Note that this is not the same as the “null” or “nil” concept discussed +in Chapter 6, which is an invalid or absent value. The cons list isn’t a commonly used data structure in Rust. Most of the time when you have a list of items in Rust, `Vec` is a better choice to use. @@ -159,7 +167,7 @@ but by starting with the cons list in this chapter, we can explore how boxes let us define a recursive data type without much distraction. Listing 15-2 contains an enum definition for a cons list. Note that this code -won’t compile yet because the `List` type doesn’t have a known size, which +won’t compile yet, because the `List` type doesn’t have a known size, which we’ll demonstrate. src/main.rs @@ -238,9 +246,9 @@ error: could not compile `cons-list` (bin "cons-list") due to 2 previous errors Listing 15-4: The error we get when attempting to define a recursive enum The error shows this type “has infinite size.” The reason is that we’ve defined -`List` with a variant that is recursive: it holds another value of itself +`List` with a variant that is recursive: It holds another value of itself directly. As a result, Rust can’t figure out how much space it needs to store a -`List` value. Let’s break down why we get this error. First we’ll look at how +`List` value. Let’s break down why we get this error. First, we’ll look at how Rust decides how much space it needs to store a value of a non-recursive type. #### Computing the Size of a Non-Recursive Type @@ -273,12 +281,16 @@ type needs, the compiler looks at the variants, starting with the `Cons` variant. The `Cons` variant holds a value of type `i32` and a value of type `List`, and this process continues infinitely, as shown in Figure 15-1. -An infinite Cons list: a rectangle labeled 'Cons' split into two smaller rectangles. The first smaller rectangle holds the label 'i32', and the second smaller rectangle holds the label 'Cons' and a smaller version of the outer 'Cons' rectangle. The 'Cons' rectangles continue to hold smaller and smaller versions of themselves until the smallest comfortably-sized rectangle holds an infinity symbol, indicating that this repetition goes on forever +An infinite Cons list: a rectangle labeled 'Cons' split into two smaller rectangles. The first smaller rectangle holds the label 'i32', and the second smaller rectangle holds the label 'Cons' and a smaller version of the outer 'Cons' rectangle. The 'Cons' rectangles continue to hold smaller and smaller versions of themselves until the smallest comfortably sized rectangle holds an infinity symbol, indicating that this repetition goes on forever. Figure 15-1: An infinite `List` consisting of infinite `Cons` variants -#### Using Box to Get a Recursive Type with a Known Size + + + + +#### Getting a Recursive Type with a Known Size Because Rust can’t figure out how much space to allocate for recursively defined types, the compiler gives an error with this helpful suggestion: @@ -299,7 +311,7 @@ directly, we should change the data structure to store the value indirectly by storing a pointer to the value instead. Because a `Box` is a pointer, Rust always knows how much space a `Box` -needs: a pointer’s size doesn’t change based on the amount of data it’s +needs: A pointer’s size doesn’t change based on the amount of data it’s pointing to. This means we can put a `Box` inside the `Cons` variant instead of another `List` value directly. The `Box` will point to the next `List` value that will be on the heap rather than inside the `Cons` variant. @@ -325,19 +337,19 @@ fn main() { } ``` -Listing 15-5: Definition of `List` that uses `Box` in order to have a known size +Listing 15-5: The definition of `List` that uses `Box` in order to have a known size -The `Cons` variant needs the size of an `i32` plus the space to store the -box’s pointer data. The `Nil` variant stores no values, so it needs less space -on the stack than the `Cons` variant. We now know that any `List` value will -take up the size of an `i32` plus the size of a box’s pointer data. By using a -box, we’ve broken the infinite, recursive chain, so the compiler can figure out -the size it needs to store a `List` value. Figure 15-2 shows what the `Cons` +The `Cons` variant needs the size of an `i32` plus the space to store the box’s +pointer data. The `Nil` variant stores no values, so it needs less space on the +stack than the `Cons` variant. We now know that any `List` value will take up +the size of an `i32` plus the size of a box’s pointer data. By using a box, +we’ve broken the infinite, recursive chain, so the compiler can figure out the +size it needs to store a `List` value. Figure 15-2 shows what the `Cons` variant looks like now. -A rectangle labeled 'Cons' split into two smaller rectangles. The first smaller rectangle holds the label 'i32', and the second smaller rectangle holds the label 'Box' with one inner rectangle that contains the label 'usize', representing the finite size of the box's pointer +A rectangle labeled 'Cons' split into two smaller rectangles. The first smaller rectangle holds the label 'i32', and the second smaller rectangle holds the label 'Box' with one inner rectangle that contains the label 'usize', representing the finite size of the box's pointer. -Figure 15-2: A `List` that is not infinitely sized +Figure 15-2: A `List` that is not infinitely sized, because `Cons` holds a `Box` Boxes provide only the indirection and heap allocation; they don’t have any @@ -355,11 +367,12 @@ even more important to the functionality provided by the other smart pointer types we’ll discuss in the rest of this chapter. Let’s explore these two traits in more detail. -## Treating Smart Pointers Like Regular References with Deref - - + + + +## Treating Smart Pointers Like Regular References Implementing the `Deref` trait allows you to customize the behavior of the *dereference operator* `*` (not to be confused with the multiplication or glob @@ -368,14 +381,14 @@ treated like a regular reference, you can write code that operates on references and use that code with smart pointers too. Let’s first look at how the dereference operator works with regular references. -Then we’ll try to define a custom type that behaves like `Box`, and see why +Then, we’ll try to define a custom type that behaves like `Box` and see why the dereference operator doesn’t work like a reference on our newly defined type. We’ll explore how implementing the `Deref` trait makes it possible for -smart pointers to work in ways similar to references. Then we’ll look at -Rust’s *deref coercion* feature and how it lets us work with either references -or smart pointers. +smart pointers to work in ways similar to references. Then, we’ll look at +Rust’s deref coercion feature and how it lets us work with either references or +smart pointers. - + @@ -404,9 +417,9 @@ Listing 15-6: Using the dereference operator to follow a reference to an `i32` v The variable `x` holds an `i32` value `5`. We set `y` equal to a reference to `x`. We can assert that `x` is equal to `5`. However, if we want to make an assertion about the value in `y`, we have to use `*y` to follow the reference -to the value it’s pointing to (hence *dereference*) so the compiler can compare -the actual value. Once we dereference `y`, we have access to the integer value -`y` is pointing to that we can compare with `5`. +to the value it’s pointing to (hence, *dereference*) so that the compiler can +compare the actual value. Once we dereference `y`, we have access to the +integer value `y` is pointing to that we can compare with `5`. If we tried to write `assert_eq!(5, y);` instead, we would get this compilation error: @@ -463,11 +476,11 @@ that enables us to use the dereference operator by defining our own box type. Let’s build a wrapper type similar to the `Box` type provided by the standard library to experience how smart pointer types behave differently from -references by default. Then we’ll look at how to add the ability to use the +references by default. Then, we’ll look at how to add the ability to use the dereference operator. > Note: There’s one big difference between the `MyBox` type we’re about to -> build and the real `Box`: our version will not store its data on the heap. +> build and the real `Box`: Our version will not store its data on the heap. > We are focusing this example on `Deref`, so where the data is actually stored > is less important than the pointer-like behavior. @@ -489,15 +502,15 @@ impl MyBox { Listing 15-8: Defining a `MyBox` type -We define a struct named `MyBox` and declare a generic parameter `T` because -we want our type to hold values of any type. The `MyBox` type is a tuple struct +We define a struct named `MyBox` and declare a generic parameter `T` because we +want our type to hold values of any type. The `MyBox` type is a tuple struct with one element of type `T`. The `MyBox::new` function takes one parameter of type `T` and returns a `MyBox` instance that holds the value passed in. Let’s try adding the `main` function in Listing 15-7 to Listing 15-8 and changing it to use the `MyBox` type we’ve defined instead of `Box`. The -code in Listing 15-9 won’t compile because Rust doesn’t know how to dereference -`MyBox`. +code in Listing 15-9 won’t compile, because Rust doesn’t know how to +dereference `MyBox`. src/main.rs @@ -532,7 +545,7 @@ Our `MyBox` type can’t be dereferenced because we haven’t implemented tha ability on our type. To enable dereferencing with the `*` operator, we implement the `Deref` trait. - + @@ -561,21 +574,20 @@ impl Deref for MyBox { Listing 15-10: Implementing `Deref` on `MyBox` -The `type Target = T;` syntax defines an associated type for the `Deref` -trait to use. Associated types are a slightly different way of declaring a -generic parameter, but you don’t need to worry about them for now; we’ll cover -them in more detail in Chapter 20. +The `type Target = T;` syntax defines an associated type for the `Deref` trait +to use. Associated types are a slightly different way of declaring a generic +parameter, but you don’t need to worry about them for now; we’ll cover them in +more detail in Chapter 20. -We fill in the body of the `deref` method with `&self.0` so `deref` returns a -reference to the value we want to access with the `*` operator; recall from -“Using Tuple Structs Without Named Fields to Create Different -Types” in Chapter 5 that `.0` accesses the first -value in a tuple struct. The `main` function in Listing 15-9 that calls `*` on -the `MyBox` value now compiles, and the assertions pass! +We fill in the body of the `deref` method with `&self.0` so that `deref` +returns a reference to the value we want to access with the `*` operator; +recall from “Creating Different Types with Tuple Structs” in Chapter 5 that `.0` accesses the first value in a tuple struct. +The `main` function in Listing 15-9 that calls `*` on the `MyBox` value now +compiles, and the assertions pass! Without the `Deref` trait, the compiler can only dereference `&` references. The `deref` method gives the compiler the ability to take a value of any type -that implements `Deref` and call the `deref` method to get an `&` reference that +that implements `Deref` and call the `deref` method to get a reference that it knows how to dereference. When we entered `*y` in Listing 15-9, behind the scenes Rust actually ran this @@ -586,8 +598,8 @@ code: ``` Rust substitutes the `*` operator with a call to the `deref` method and then a -plain dereference so we don’t have to think about whether or not we need to -call the `deref` method. This Rust feature lets us write code that functions +plain dereference so that we don’t have to think about whether or not we need +to call the `deref` method. This Rust feature lets us write code that functions identically whether we have a regular reference or a type that implements `Deref`. @@ -604,13 +616,18 @@ Because the substitution of the `*` operator does not recurse infinitely, we end up with data of type `i32`, which matches the `5` in `assert_eq!` in Listing 15-9. -### Implicit Deref Coercions with Functions and Methods + + + + + +### Using Deref Coercion in Functions and Methods *Deref coercion* converts a reference to a type that implements the `Deref` trait into a reference to another type. For example, deref coercion can convert `&String` to `&str` because `String` implements the `Deref` trait such that it returns `&str`. Deref coercion is a convenience Rust performs on arguments to -functions and methods, and works only on types that implement the `Deref` +functions and methods, and it works only on types that implement the `Deref` trait. It happens automatically when we pass a reference to a particular type’s value as an argument to a function or method that doesn’t match the parameter type in the function or method definition. A sequence of calls to the `deref` @@ -674,7 +691,7 @@ fn main() { Listing 15-13: The code we would have to write if Rust didn’t have deref coercion -The `(*m)` dereferences the `MyBox` into a `String`. Then the `&` and +The `(*m)` dereferences the `MyBox` into a `String`. Then, the `&` and `[..]` take a string slice of the `String` that is equal to the whole string to match the signature of `hello`. This code without deref coercions is harder to read, write, and understand with all of these symbols involved. Deref coercion @@ -686,7 +703,11 @@ match the parameter’s type. The number of times that `Deref::deref` needs to b inserted is resolved at compile time, so there is no runtime penalty for taking advantage of deref coercion! -### How Deref Coercion Interacts with Mutability + + + + +### Handling Deref Coercion with Mutable References Similar to how you use the `Deref` trait to override the `*` operator on immutable references, you can use the `DerefMut` trait to override the `*` @@ -701,11 +722,11 @@ cases: The first two cases are the same except that the second implements mutability. The first case states that if you have a `&T`, and `T` implements `Deref` to -some type `U`, you can get a `&U` transparently. The second case states that the -same deref coercion happens for mutable references. +some type `U`, you can get a `&U` transparently. The second case states that +the same deref coercion happens for mutable references. The third case is trickier: Rust will also coerce a mutable reference to an -immutable one. But the reverse is *not* possible: immutable references will +immutable one. But the reverse is *not* possible: Immutable references will never coerce to mutable references. Because of the borrowing rules, if you have a mutable reference, that mutable reference must be the only reference to that data (otherwise, the program wouldn’t compile). Converting one mutable @@ -725,15 +746,15 @@ be used to release resources like files or network connections. We’re introducing `Drop` in the context of smart pointers because the functionality of the `Drop` trait is almost always used when implementing a -smart pointer. For example, when a `Box` is dropped it will deallocate the +smart pointer. For example, when a `Box` is dropped, it will deallocate the space on the heap that the box points to. In some languages, for some types, the programmer must call code to free memory or resources every time they finish using an instance of those types. Examples -include file handles, sockets, and locks. If they forget, the system might -become overloaded and crash. In Rust, you can specify that a particular bit of -code be run whenever a value goes out of scope, and the compiler will insert -this code automatically. As a result, you don’t need to be careful about +include file handles, sockets, and locks. If the programmer forgets, the system +might become overloaded and crash. In Rust, you can specify that a particular +bit of code be run whenever a value goes out of scope, and the compiler will +insert this code automatically. As a result, you don’t need to be careful about placing cleanup code everywhere in a program that an instance of a particular type is finished with—you still won’t leak resources! @@ -766,7 +787,7 @@ fn main() { let d = CustomSmartPointer { data: String::from("other stuff"), }; - println!("CustomSmartPointers created."); + println!("CustomSmartPointers created"); } ``` @@ -792,7 +813,7 @@ $ cargo run Compiling drop-example v0.1.0 (file:///projects/drop-example) Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.60s Running `target/debug/drop-example` -CustomSmartPointers created. +CustomSmartPointers created Dropping CustomSmartPointer with data `other stuff`! Dropping CustomSmartPointer with data `my stuff`! ``` @@ -804,7 +825,7 @@ give you a visual guide to how the `drop` method works; usually you would specify the cleanup code that your type needs to run rather than a print message. - + @@ -812,15 +833,14 @@ Unfortunately, it’s not straightforward to disable the automatic `drop` functionality. Disabling `drop` isn’t usually necessary; the whole point of the `Drop` trait is that it’s taken care of automatically. Occasionally, however, you might want to clean up a value early. One example is when using smart -pointers that manage locks: you might want to force the `drop` method that +pointers that manage locks: You might want to force the `drop` method that releases the lock so that other code in the same scope can acquire the lock. Rust doesn’t let you call the `Drop` trait’s `drop` method manually; instead, you have to call the `std::mem::drop` function provided by the standard library if you want to force a value to be dropped before the end of its scope. -If we try to call the `Drop` trait’s `drop` method manually by modifying the -`main` function from Listing 15-14, as shown in Listing 15-15, we’ll get a -compiler error. +Trying to call the `Drop` trait’s `drop` method manually by modifying the +`main` function from Listing 15-14 won’t work, as shown in Listing 15-15. src/main.rs @@ -829,9 +849,9 @@ fn main() { let c = CustomSmartPointer { data: String::from("some data"), }; - println!("CustomSmartPointer created."); + println!("CustomSmartPointer created"); c.drop(); - println!("CustomSmartPointer dropped before the end of main."); + println!("CustomSmartPointer dropped before the end of main"); } ``` @@ -863,10 +883,9 @@ for a function that cleans up an instance. A *destructor* is analogous to a *constructor*, which creates an instance. The `drop` function in Rust is one particular destructor. -Rust doesn’t let us call `drop` explicitly because Rust would still +Rust doesn’t let us call `drop` explicitly, because Rust would still automatically call `drop` on the value at the end of `main`. This would cause a -*double free* error because Rust would be trying to clean up the same value -twice. +double free error because Rust would be trying to clean up the same value twice. We can’t disable the automatic insertion of `drop` when a value goes out of scope, and we can’t call the `drop` method explicitly. So, if we need to force @@ -884,9 +903,9 @@ fn main() { let c = CustomSmartPointer { data: String::from("some data"), }; - println!("CustomSmartPointer created."); + println!("CustomSmartPointer created"); drop(c); - println!("CustomSmartPointer dropped before the end of main."); + println!("CustomSmartPointer dropped before the end of main"); } ``` @@ -899,22 +918,22 @@ $ cargo run Compiling drop-example v0.1.0 (file:///projects/drop-example) Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.73s Running `target/debug/drop-example` -CustomSmartPointer created. +CustomSmartPointer created Dropping CustomSmartPointer with data `some data`! -CustomSmartPointer dropped before the end of main. +CustomSmartPointer dropped before the end of main ``` The text ``Dropping CustomSmartPointer with data `some data`!`` is printed -between the `CustomSmartPointer created.` and `CustomSmartPointer dropped before the end of main.` text, showing that the `drop` method code is called to -drop `c` at that point. +between the `CustomSmartPointer created` and `CustomSmartPointer dropped before the end of main` text, showing that the `drop` method code is called to drop +`c` at that point. You can use code specified in a `Drop` trait implementation in many ways to -make cleanup convenient and safe: for instance, you could use it to create your +make cleanup convenient and safe: For instance, you could use it to create your own memory allocator! With the `Drop` trait and Rust’s ownership system, you -don’t have to remember to clean up because Rust does it automatically. +don’t have to remember to clean up, because Rust does it automatically. You also don’t have to worry about problems resulting from accidentally -cleaning up values still in use: the ownership system that makes sure +cleaning up values still in use: The ownership system that makes sure references are always valid also ensures that `drop` gets called only once when the value is no longer being used. @@ -922,9 +941,9 @@ Now that we’ve examined `Box` and some of the characteristics of smart pointers, let’s look at a few other smart pointers defined in the standard library. -## Rc, the Reference Counted Smart Pointer +## Rc, the Reference-Counted Smart Pointer -In the majority of cases, ownership is clear: you know exactly which variable +In the majority of cases, ownership is clear: You know exactly which variable owns a given value. However, there are cases when a single value might have multiple owners. For example, in graph data structures, multiple edges might point to the same node, and that node is conceptually owned by all of the edges @@ -953,21 +972,30 @@ Note that `Rc` is only for use in single-threaded scenarios. When we discuss concurrency in Chapter 16, we’ll cover how to do reference counting in multithreaded programs. -### Using Rc to Share Data + + + + +### Sharing Data Let’s return to our cons list example in Listing 15-5. Recall that we defined it using `Box`. This time, we’ll create two lists that both share ownership of a third list. Conceptually, this looks similar to Figure 15-3. -A linked list with the label 'a' pointing to three elements: the first element contains the integer 5 and points to the second element. The second element contains the integer 10 and points to the third element. The third element contains the value 'Nil' that signifies the end of the list; it does not point anywhere. A linked list with the label 'b' points to an element that contains the integer 3 and points to the first element of list 'a'. A linked list with the label 'c' points to an element that contains the integer 4 and also points to the first element of list 'a', so that the tail of lists 'b' and 'c' are both list 'a' +A linked list with the label 'a' pointing to three elements. The first element contains the integer 5 and points to the second element. Th
+e second element contains the integer 10 and points to the third element. The third element contains the value 'Nil' that signifies the end of the l
+ist; it does not point anywhere. A linked list with the label 'b' points to an element that contains the integer 3 and points to the first element o
+f list 'a'. A linked list with the label 'c' points to an element that contains the integer 4 and also points to the first element of list 'a' so th
+at the tails of lists 'b' and 'c' are both list 'a'. Figure 15-3: Two lists, `b` and `c`, sharing ownership of a third list, `a` -We’ll create list `a` that contains `5` and then `10`. Then we’ll make two more -lists: `b` that starts with `3` and `c` that starts with `4`. Both `b` and `c` -lists will then continue on to the first `a` list containing `5` and `10`. In -other words, both lists will share the first list containing `5` and `10`. +We’ll create list `a` that contains `5` and then `10`. Then, we’ll make two +more lists: `b` that starts with `3` and `c` that starts with `4`. Both the `b` +and `c` lists will then continue on to the first `a` list containing `5` and +`10`. In other words, both lists will share the first list containing `5` and +`10`. Trying to implement this scenario using our definition of `List` with `Box` won’t work, as shown in Listing 15-17. @@ -1068,13 +1096,19 @@ increase the reference count. When looking for performance problems in the code, we only need to consider the deep-copy clones and can disregard calls to `Rc::clone`. -### Cloning an Rc Increases the Reference Count + + + -Let’s change our working example in Listing 15-18 so we can see the reference -counts changing as we create and drop references to the `Rc` in `a`. +### Cloning to Increase the Reference Count -In Listing 15-19, we’ll change `main` so it has an inner scope around list `c`; -then we can see how the reference count changes when `c` goes out of scope. +Let’s change our working example in Listing 15-18 so that we can see the +reference counts changing as we create and drop references to the `Rc` in +`a`. + +In Listing 15-19, we’ll change `main` so that it has an inner scope around list +`c`; then, we can see how the reference count changes when `c` goes out of +scope. src/main.rs @@ -1115,15 +1149,15 @@ count after creating c = 3 count after c goes out of scope = 2 ``` -We can see that the `Rc` in `a` has an initial reference count of 1; then -each time we call `clone`, the count goes up by 1. When `c` goes out of scope, -the count goes down by 1. We don’t have to call a function to decrease the -reference count like we have to call `Rc::clone` to increase the reference -count: the implementation of the `Drop` trait decreases the reference count +We can see that the `Rc` in `a` has an initial reference count of 1; +then, each time we call `clone`, the count goes up by 1. When `c` goes out of +scope, the count goes down by 1. We don’t have to call a function to decrease +the reference count like we have to call `Rc::clone` to increase the reference +count: The implementation of the `Drop` trait decreases the reference count automatically when an `Rc` value goes out of scope. What we can’t see in this example is that when `b` and then `a` go out of scope -at the end of `main`, the count is then 0, and the `Rc` is cleaned up +at the end of `main`, the count is 0, and the `Rc` is cleaned up completely. Using `Rc` allows a single value to have multiple owners, and the count ensures that the value remains valid as long as any of the owners still exist. @@ -1131,7 +1165,7 @@ still exist. Via immutable references, `Rc` allows you to share data between multiple parts of your program for reading only. If `Rc` allowed you to have multiple mutable references too, you might violate one of the borrowing rules discussed -in Chapter 4: multiple mutable borrows to the same place can cause data races +in Chapter 4: Multiple mutable borrows to the same place can cause data races and inconsistencies. But being able to mutate data is very useful! In the next section, we’ll discuss the interior mutability pattern and the `RefCell` type that you can use in conjunction with an `Rc` to work with this @@ -1155,10 +1189,14 @@ safe API, and the outer type is still immutable. Let’s explore this concept by looking at the `RefCell` type that follows the interior mutability pattern. -### Enforcing Borrowing Rules at Runtime with RefCell + + + + +### Enforcing Borrowing Rules at Runtime Unlike `Rc`, the `RefCell` type represents single ownership over the data -it holds. So what makes `RefCell` different from a type like `Box`? +it holds. So, what makes `RefCell` different from a type like `Box`? Recall the borrowing rules you learned in Chapter 4: * At any given time, you can have *either* one mutable reference or any number @@ -1180,7 +1218,7 @@ The advantage of checking the borrowing rules at runtime instead is that certain memory-safe scenarios are then allowed, where they would’ve been disallowed by the compile-time checks. Static analysis, like the Rust compiler, is inherently conservative. Some properties of code are impossible to detect by -analyzing the code: the most famous example is the Halting Problem, which is +analyzing the code: The most famous example is the Halting Problem, which is beyond the scope of this book but is an interesting topic to research. Because some analysis is impossible, if the Rust compiler can’t be sure the @@ -1208,11 +1246,15 @@ Here is a recap of the reasons to choose `Box`, `Rc`, or `RefCell`: mutate the value inside the `RefCell` even when the `RefCell` is immutable. -Mutating the value inside an immutable value is the *interior mutability* +Mutating the value inside an immutable value is the interior mutability pattern. Let’s look at a situation in which interior mutability is useful and examine how it’s possible. -### Interior Mutability: A Mutable Borrow to an Immutable Value + + + + +### Using Interior Mutability A consequence of the borrowing rules is that when you have an immutable value, you can’t borrow it mutably. For example, this code won’t compile: @@ -1248,7 +1290,7 @@ However, there are situations in which it would be useful for a value to mutate itself in its methods but appear immutable to other code. Code outside the value’s methods would not be able to mutate the value. Using `RefCell` is one way to get the ability to have interior mutability, but `RefCell` -doesn’t get around the borrowing rules completely: the borrow checker in the +doesn’t get around the borrowing rules completely: The borrow checker in the compiler allows this interior mutability, and the borrowing rules are checked at runtime instead. If you violate the rules, you’ll get a `panic!` instead of a compiler error. @@ -1256,7 +1298,11 @@ a compiler error. Let’s work through a practical example where we can use `RefCell` to mutate an immutable value and see why that is useful. -#### A Use Case for Interior Mutability: Mock Objects + + + + +#### Testing with Mock Objects Sometimes during testing a programmer will use a type in place of another type, in order to observe particular behavior and assert that it’s implemented @@ -1264,7 +1310,7 @@ correctly. This placeholder type is called a *test double*. Think of it in the sense of a stunt double in filmmaking, where a person steps in and substitutes for an actor to do a particularly tricky scene. Test doubles stand in for other types when we’re running tests. *Mock objects* are specific types of test -doubles that record what happens during a test so you can assert that the +doubles that record what happens during a test so that you can assert that the correct actions took place. Rust doesn’t have objects in the same sense as other languages have objects, @@ -1272,7 +1318,7 @@ and Rust doesn’t have mock object functionality built into the standard librar as some other languages do. However, you can definitely create a struct that will serve the same purposes as a mock object. -Here’s the scenario we’ll test: we’ll create a library that tracks a value +Here’s the scenario we’ll test: We’ll create a library that tracks a value against a maximum value and sends messages based on how close to the maximum value the current value is. This library could be used to keep track of a user’s quota for the number of API calls they’re allowed to make, for example. @@ -1280,10 +1326,10 @@ user’s quota for the number of API calls they’re allowed to make, for exampl Our library will only provide the functionality of tracking how close to the maximum a value is and what the messages should be at what times. Applications that use our library will be expected to provide the mechanism for sending the -messages: the application could put a message in the application, send an email, -send a text message, or do something else. The library doesn’t need to know that -detail. All it needs is something that implements a trait we’ll provide called -`Messenger`. Listing 15-20 shows the library code. +messages: The application could show the message to the user directly, send an +email, send a text message, or do something else. The library doesn’t need to +know that detail. All it needs is something that implements a trait we’ll +provide, called `Messenger`. Listing 15-20 shows the library code. src/lib.rs @@ -1338,8 +1384,8 @@ is that we want to test the behavior of the `set_value` method on the `LimitTracker`. We can change what we pass in for the `value` parameter, but `set_value` doesn’t return anything for us to make assertions on. We want to be able to say that if we create a `LimitTracker` with something that implements -the `Messenger` trait and a particular value for `max`, when we pass different -numbers for `value` the messenger is told to send the appropriate messages. +the `Messenger` trait and a particular value for `max`, the messenger is told +to send the appropriate messages when we pass different numbers for `value`. We need a mock object that, instead of sending an email or text message when we call `send`, will only keep track of the messages it’s told to send. We can @@ -1391,18 +1437,18 @@ This test code defines a `MockMessenger` struct that has a `sent_messages` field with a `Vec` of `String` values to keep track of the messages it’s told to send. We also define an associated function `new` to make it convenient to create new `MockMessenger` values that start with an empty list of messages. We -then implement the `Messenger` trait for `MockMessenger` so we can give a +then implement the `Messenger` trait for `MockMessenger` so that we can give a `MockMessenger` to a `LimitTracker`. In the definition of the `send` method, we take the message passed in as a parameter and store it in the `MockMessenger` list of `sent_messages`. In the test, we’re testing what happens when the `LimitTracker` is told to set -`value` to something that is more than 75 percent of the `max` value. First we +`value` to something that is more than 75 percent of the `max` value. First, we create a new `MockMessenger`, which will start with an empty list of messages. -Then we create a new `LimitTracker` and give it a reference to the new +Then, we create a new `LimitTracker` and give it a reference to the new `MockMessenger` and a `max` value of `100`. We call the `set_value` method on the `LimitTracker` with a value of `80`, which is more than 75 percent of 100. -Then we assert that the list of messages that the `MockMessenger` is keeping +Then, we assert that the list of messages that the `MockMessenger` is keeping track of should now have one message in it. However, there’s one problem with this test, as shown here: @@ -1429,7 +1475,7 @@ For more information about this error, try `rustc --explain E0596`. error: could not compile `limit-tracker` (lib test) due to 1 previous error ``` -We can’t modify the `MockMessenger` to keep track of the messages because the +We can’t modify the `MockMessenger` to keep track of the messages, because the `send` method takes an immutable reference to `self`. We also can’t take the suggestion from the error text to use `&mut self` in both the `impl` method and the trait definition. We do not want to change the `Messenger` trait solely for @@ -1437,9 +1483,9 @@ the sake of testing. Instead, we need to find a way to make our test code work correctly with our existing design. This is a situation in which interior mutability can help! We’ll store the -`sent_messages` within a `RefCell`, and then the `send` method will be -able to modify `sent_messages` to store the messages we’ve seen. Listing 15-22 -shows what that looks like. +`sent_messages` within a `RefCell`, and then the `send` method will be able +to modify `sent_messages` to store the messages we’ve seen. Listing 15-22 shows +what that looks like. src/lib.rs @@ -1486,16 +1532,20 @@ For the implementation of the `send` method, the first parameter is still an immutable borrow of `self`, which matches the trait definition. We call `borrow_mut` on the `RefCell>` in `self.sent_messages` to get a mutable reference to the value inside the `RefCell>`, which is the -vector. Then we can call `push` on the mutable reference to the vector to keep +vector. Then, we can call `push` on the mutable reference to the vector to keep track of the messages sent during the test. -The last change we have to make is in the assertion: to see how many items are +The last change we have to make is in the assertion: To see how many items are in the inner vector, we call `borrow` on the `RefCell>` to get an immutable reference to the vector. Now that you’ve seen how to use `RefCell`, let’s dig into how it works! -#### Keeping Track of Borrows at Runtime with RefCell + + + + +#### Tracking Borrows at Runtime When creating immutable and mutable references, we use the `&` and `&mut` syntax, respectively. With `RefCell`, we use the `borrow` and `borrow_mut` @@ -1535,8 +1585,8 @@ src/lib.rs Listing 15-23: Creating two mutable references in the same scope to see that `RefCell` will panic We create a variable `one_borrow` for the `RefMut` smart pointer returned -from `borrow_mut`. Then we create another mutable borrow in the same way in the -variable `two_borrow`. This makes two mutable references in the same scope, +from `borrow_mut`. Then, we create another mutable borrow in the same way in +the variable `two_borrow`. This makes two mutable references in the same scope, which isn’t allowed. When we run the tests for our library, the code in Listing 15-23 will compile without any errors, but the test will fail: @@ -1580,23 +1630,25 @@ in a context where only immutable values are allowed. You can use `RefCell` despite its trade-offs to get more functionality than regular references provide. - + + -### Allowing Multiple Owners of Mutable Data with Rc and RefCell +### Allowing Multiple Owners of Mutable Data A common way to use `RefCell` is in combination with `Rc`. Recall that `Rc` lets you have multiple owners of some data, but it only gives immutable access to that data. If you have an `Rc` that holds a `RefCell`, you can get a value that can have multiple owners *and* that you can mutate! -For example, recall the cons list example in Listing 15-18 where we used `Rc` -to allow multiple lists to share ownership of another list. Because `Rc` -holds only immutable values, we can’t change any of the values in the list once -we’ve created them. Let’s add in `RefCell` for its ability to change the -values in the lists. Listing 15-24 shows that by using a `RefCell` in the -`Cons` definition, we can modify the value stored in all the lists. +For example, recall the cons list example in Listing 15-18 where we used +`Rc` to allow multiple lists to share ownership of another list. Because +`Rc` holds only immutable values, we can’t change any of the values in the +list once we’ve created them. Let’s add in `RefCell` for its ability to +change the values in the lists. Listing 15-24 shows that by using a +`RefCell` in the `Cons` definition, we can modify the value stored in all +the lists. src/main.rs @@ -1630,11 +1682,11 @@ fn main() { Listing 15-24: Using `Rc>` to create a `List` that we can mutate We create a value that is an instance of `Rc>` and store it in a -variable named `value` so we can access it directly later. Then we create a -`List` in `a` with a `Cons` variant that holds `value`. We need to clone -`value` so both `a` and `value` have ownership of the inner `5` value rather -than transferring ownership from `value` to `a` or having `a` borrow from -`value`. +variable named `value` so that we can access it directly later. Then, we create +a `List` in `a` with a `Cons` variant that holds `value`. We need to clone +`value` so that both `a` and `value` have ownership of the inner `5` value +rather than transferring ownership from `value` to `a` or having `a` borrow +from `value`. We wrap the list `a` in an `Rc` so that when we create lists `b` and `c`, they can both refer to `a`, which is what we did in Listing 15-18. @@ -1642,7 +1694,7 @@ they can both refer to `a`, which is what we did in Listing 15-18. After we’ve created the lists in `a`, `b`, and `c`, we want to add 10 to the value in `value`. We do this by calling `borrow_mut` on `value`, which uses the automatic dereferencing feature we discussed in “Where’s the `->` -Operator?”) in Chapter 5 to dereference +Operator?” in Chapter 5 to dereference the `Rc` to the inner `RefCell` value. The `borrow_mut` method returns a `RefMut` smart pointer, and we use the dereference operator on it and change the inner value. @@ -1662,9 +1714,9 @@ c after = Cons(RefCell { value: 4 }, Cons(RefCell { value: 15 }, Nil)) This technique is pretty neat! By using `RefCell`, we have an outwardly immutable `List` value. But we can use the methods on `RefCell` that provide -access to its interior mutability so we can modify our data when we need to. -The runtime checks of the borrowing rules protect us from data races, and it’s -sometimes worth trading a bit of speed for this flexibility in our data +access to its interior mutability so that we can modify our data when we need +to. The runtime checks of the borrowing rules protect us from data races, and +it’s sometimes worth trading a bit of speed for this flexibility in our data structures. Note that `RefCell` does not work for multithreaded code! `Mutex` is the thread-safe version of `RefCell`, and we’ll discuss `Mutex` in Chapter 16. @@ -1675,7 +1727,7 @@ Rust’s memory safety guarantees make it difficult, but not impossible, to accidentally create memory that is never cleaned up (known as a *memory leak*). Preventing memory leaks entirely is not one of Rust’s guarantees, meaning memory leaks are memory safe in Rust. We can see that Rust allows memory leaks -by using `Rc` and `RefCell`: it’s possible to create references where +by using `Rc` and `RefCell`: It’s possible to create references where items refer to each other in a cycle. This creates memory leaks because the reference count of each item in the cycle will never reach 0, and the values will never be dropped. @@ -1707,11 +1759,9 @@ impl List { } } } - -fn main() {} ``` -Listing 15-25: A cons list definition that holds a `RefCell` so we can modify what a `Cons` variant is referring to +Listing 15-25: A cons list definition that holds a `RefCell` so that we can modify what a `Cons` variant is referring to We’re using another variation of the `List` definition from Listing 15-5. The second element in the `Cons` variant is now `RefCell>`, meaning that @@ -1722,7 +1772,7 @@ second item if we have a `Cons` variant. In Listing 15-26, we’re adding a `main` function that uses the definitions in Listing 15-25. This code creates a list in `a` and a list in `b` that points to -the list in `a`. Then it modifies the list in `a` to point to `b`, creating a +the list in `a`. Then, it modifies the list in `a` to point to `b`, creating a reference cycle. There are `println!` statements along the way to show what the reference counts are at various points in this process. @@ -1758,14 +1808,14 @@ Listing 15-26: Creating a reference cycle of two `List` values pointing to each We create an `Rc` instance holding a `List` value in the variable `a` with an initial list of `5, Nil`. We then create an `Rc` instance holding -another `List` value in the variable `b` that contains the value `10` and points -to the list in `a`. +another `List` value in the variable `b` that contains the value `10` and +points to the list in `a`. -We modify `a` so it points to `b` instead of `Nil`, creating a cycle. We do -that by using the `tail` method to get a reference to the `RefCell>` -in `a`, which we put in the variable `link`. Then we use the `borrow_mut` -method on the `RefCell>` to change the value inside from an `Rc` -that holds a `Nil` value to the `Rc` in `b`. +We modify `a` so that it points to `b` instead of `Nil`, creating a cycle. We +do that by using the `tail` method to get a reference to the +`RefCell>` in `a`, which we put in the variable `link`. Then, we use +the `borrow_mut` method on the `RefCell>` to change the value inside +from an `Rc` that holds a `Nil` value to the `Rc` in `b`. When we run this code, keeping the last `println!` commented out for the moment, we’ll get this output: @@ -1786,29 +1836,29 @@ a rc count after changing a = 2 The reference count of the `Rc` instances in both `a` and `b` is 2 after we change the list in `a` to point to `b`. At the end of `main`, Rust drops the -variable `b`, which decreases the reference count of the `b` `Rc` instance -from 2 to 1. The memory that `Rc` has on the heap won’t be dropped at -this point because its reference count is 1, not 0. Then Rust drops `a`, which -decreases the reference count of the `a` `Rc` instance from 2 to 1 as -well. This instance’s memory can’t be dropped either, because the other +variable `b`, which decreases the reference count of the `b` `Rc` +instance from 2 to 1. The memory that `Rc` has on the heap won’t be +dropped at this point because its reference count is 1, not 0. Then, Rust drops +`a`, which decreases the reference count of the `a` `Rc` instance from 2 +to 1 as well. This instance’s memory can’t be dropped either, because the other `Rc` instance still refers to it. The memory allocated to the list will -remain uncollected forever. To visualize this reference cycle, we’ve created the -diagram in Figure 15-4. +remain uncollected forever. To visualize this reference cycle, we’ve created +the diagram in Figure 15-4. -A rectangle labeled 'a' that points to a rectangle containing the integer 5. A rectangle labeled 'b' that points to a rectangle containing the integer 10. The rectangle containing 5 points to the rectangle containing 10, and the rectangle containing 10 points back to the rectangle containing 5, creating a cycle +A rectangle labeled 'a' that points to a rectangle containing the integer 5. A rectangle labeled 'b' that points to a rectangle containing the integer 10. The rectangle containing 5 points to the rectangle containing 10, and the rectangle containing 10 points back to the rectangle containing 5, creating a cycle. Figure 15-4: A reference cycle of lists `a` and `b` pointing to each other -If you uncomment the last `println!` and run the program, Rust will try to print -this cycle with `a` pointing to `b` pointing to `a` and so forth until it +If you uncomment the last `println!` and run the program, Rust will try to +print this cycle with `a` pointing to `b` pointing to `a` and so forth until it overflows the stack. -Compared to a real-world program, the consequences of creating a reference cycle -in this example aren’t very dire: right after we create the reference cycle, -the program ends. However, if a more complex program allocated lots of memory -in a cycle and held onto it for a long time, the program would use more memory -than it needed and might overwhelm the system, causing it to run out of +Compared to a real-world program, the consequences of creating a reference +cycle in this example aren’t very dire: Right after we create the reference +cycle, the program ends. However, if a more complex program allocated lots of +memory in a cycle and held onto it for a long time, the program would use more +memory than it needed and might overwhelm the system, causing it to run out of available memory. Creating reference cycles is not easily done, but it’s not impossible either. @@ -1829,21 +1879,22 @@ Let’s look at an example using graphs made up of parent nodes and child nodes to see when non-ownership relationships are an appropriate way to prevent reference cycles. - + ### Preventing Reference Cycles Using Weak -So far, we’ve demonstrated that calling `Rc::clone` increases the `strong_count` -of an `Rc` instance, and an `Rc` instance is only cleaned up if its -`strong_count` is 0. You can also create a weak reference to the value within -an `Rc` instance by calling `Rc::downgrade` and passing a reference to the -`Rc`. *Strong references* are how you can share ownership of an `Rc` -instance. *Weak references* don’t express an ownership relationship, and their -count doesn’t affect when an `Rc` instance is cleaned up. They won’t cause a -reference cycle because any cycle involving some weak references will be broken -once the strong reference count of values involved is 0. +So far, we’ve demonstrated that calling `Rc::clone` increases the +`strong_count` of an `Rc` instance, and an `Rc` instance is only cleaned +up if its `strong_count` is 0. You can also create a weak reference to the +value within an `Rc` instance by calling `Rc::downgrade` and passing a +reference to the `Rc`. *Strong references* are how you can share ownership +of an `Rc` instance. *Weak references* don’t express an ownership +relationship, and their count doesn’t affect when an `Rc` instance is +cleaned up. They won’t cause a reference cycle, because any cycle involving +some weak references will be broken once the strong reference count of values +involved is 0. When you call `Rc::downgrade`, you get a smart pointer of type `Weak`. Instead of increasing the `strong_count` in the `Rc` instance by 1, calling @@ -1862,14 +1913,18 @@ Rust will ensure that the `Some` case and the `None` case are handled, and there won’t be an invalid pointer. As an example, rather than using a list whose items know only about the next -item, we’ll create a tree whose items know about their children items *and* -their parent items. +item, we’ll create a tree whose items know about their child items *and* their +parent items. + + + + -#### Creating a Tree Data Structure: A Node with Child Nodes +#### Creating a Tree Data Structure To start, we’ll build a tree with nodes that know about their child nodes. We’ll create a struct named `Node` that holds its own `i32` value as well as -references to its children `Node` values: +references to its child `Node` values: Filename: src/main.rs @@ -1885,8 +1940,8 @@ struct Node { ``` We want a `Node` to own its children, and we want to share that ownership with -variables so we can access each `Node` in the tree directly. To do this, we -define the `Vec` items to be values of type `Rc`. We also want to +variables so that we can access each `Node` in the tree directly. To do this, +we define the `Vec` items to be values of type `Rc`. We also want to modify which nodes are children of another node, so we have a `RefCell` in `children` around the `Vec>`. @@ -1929,11 +1984,11 @@ create a reference cycle with `leaf.parent` pointing to `branch` and values to never be 0. Thinking about the relationships another way, a parent node should own its -children: if a parent node is dropped, its child nodes should be dropped as -well. However, a child should not own its parent: if we drop a child node, the +children: If a parent node is dropped, its child nodes should be dropped as +well. However, a child should not own its parent: If we drop a child node, the parent should still exist. This is a case for weak references! -So instead of `Rc`, we’ll make the type of `parent` use `Weak`, +So, instead of `Rc`, we’ll make the type of `parent` use `Weak`, specifically a `RefCell>`. Now our `Node` struct definition looks like this: @@ -1951,8 +2006,8 @@ struct Node { } ``` -A node will be able to refer to its parent node but doesn’t own its parent. -In Listing 15-28, we update `main` to use this new definition so the `leaf` +A node will be able to refer to its parent node but doesn’t own its parent. In +Listing 15-28, we update `main` to use this new definition so that the `leaf` node will have a way to refer to its parent, `branch`. src/main.rs @@ -1994,16 +2049,15 @@ leaf parent = None ``` When we create the `branch` node, it will also have a new `Weak` -reference in the `parent` field because `branch` doesn’t have a parent node. -We still have `leaf` as one of the children of `branch`. Once we have the -`Node` instance in `branch`, we can modify `leaf` to give it a `Weak` -reference to its parent. We use the `borrow_mut` method on the -`RefCell>` in the `parent` field of `leaf`, and then we use the -`Rc::downgrade` function to create a `Weak` reference to `branch` from -the `Rc` in `branch`. +reference in the `parent` field because `branch` doesn’t have a parent node. We +still have `leaf` as one of the children of `branch`. Once we have the `Node` +instance in `branch`, we can modify `leaf` to give it a `Weak` reference +to its parent. We use the `borrow_mut` method on the `RefCell>` in +the `parent` field of `leaf`, and then we use the `Rc::downgrade` function to +create a `Weak` reference to `branch` from the `Rc` in `branch`. When we print the parent of `leaf` again, this time we’ll get a `Some` variant -holding `branch`: now `leaf` can access its parent! When we print `leaf`, we +holding `branch`: Now `leaf` can access its parent! When we print `leaf`, we also avoid the cycle that eventually ended in a stack overflow like we had in Listing 15-26; the `Weak` references are printed as `(Weak)`: @@ -2080,7 +2134,7 @@ count of 0. In the inner scope, we create `branch` and associate it with will have a strong count of 1 and a weak count of 1 (for `leaf.parent` pointing to `branch` with a `Weak`). When we print the counts in `leaf`, we’ll see it will have a strong count of 2 because `branch` now has a clone of the -`Rc` of `leaf` stored in `branch.children`, but will still have a weak +`Rc` of `leaf` stored in `branch.children` but will still have a weak count of 0. When the inner scope ends, `branch` goes out of scope and the strong count of @@ -2106,7 +2160,7 @@ This chapter covered how to use smart pointers to make different guarantees and trade-offs from those Rust makes by default with regular references. The `Box` type has a known size and points to data allocated on the heap. The `Rc` type keeps track of the number of references to data on the heap so -that data can have multiple owners. The `RefCell` type with its interior +that the data can have multiple owners. The `RefCell` type with its interior mutability gives us a type that we can use when we need an immutable type but need to change an inner value of that type; it also enforces the borrowing rules at runtime instead of at compile time. diff --git a/nostarch/chapter16.md b/nostarch/chapter16.md index c34a258c9c..1877883b2f 100644 --- a/nostarch/chapter16.md +++ b/nostarch/chapter16.md @@ -13,7 +13,7 @@ major goals. *Concurrent programming*, in which different parts of a program execute independently, and *parallel programming*, in which different parts of a program execute at the same time, are becoming increasingly important as more computers take advantage of their multiple processors. Historically, -programming in these contexts has been difficult and error prone. Rust hopes to +programming in these contexts has been difficult and error-prone. Rust hopes to change that. Initially, the Rust team thought that ensuring memory safety and preventing @@ -144,7 +144,7 @@ hi number 5 from the spawned thread! The calls to `thread::sleep` force a thread to stop its execution for a short duration, allowing a different thread to run. The threads will probably take -turns, but that isn’t guaranteed: it depends on how your operating system +turns, but that isn’t guaranteed: It depends on how your operating system schedules the threads. In this run, the main thread printed first, even though the print statement from the spawned thread appears first in the code. And even though we told the spawned thread to print until `i` is `9`, it only got to `5` @@ -154,7 +154,11 @@ If you run this code and only see output from the main thread, or don’t see an overlap, try increasing the numbers in the ranges to create more opportunities for the operating system to switch between the threads. -### Waiting for All Threads to Finish Using join Handles + + + + +### Waiting for All Threads to Finish The code in Listing 16-1 not only stops the spawned thread prematurely most of the time due to the main thread ending, but because there is no guarantee on @@ -286,7 +290,7 @@ another. In “Capturing References or Moving Ownership” in Chapter 13, we dis concentrate more on the interaction between `move` and `thread::spawn`. Notice in Listing 16-1 that the closure we pass to `thread::spawn` takes no -arguments: we’re not using any data from the main thread in the spawned +arguments: We’re not using any data from the main thread in the spawned thread’s code. To use data from the main thread in the spawned thread, the spawned thread’s closure must capture the values it needs. Listing 16-3 shows an attempt to create a vector in the main thread and use it in the spawned @@ -456,10 +460,14 @@ ownership rules. Now that we’ve covered what threads are and the methods supplied by the thread API, let’s look at some situations in which we can use threads. -## Using Message Passing to Transfer Data Between Threads + + + -One increasingly popular approach to ensuring safe concurrency is *message -passing*, where threads or actors communicate by sending each other messages +## Transfer Data Between Threads with Message Passing + +One increasingly popular approach to ensuring safe concurrency is message +passing, where threads or actors communicate by sending each other messages containing data. Here’s the idea in a slogan from the Go language documentation at *https://golang.org/doc/effective_go.html#concurrency*: “Do not communicate by sharing memory; instead, share memory by communicating.” @@ -506,7 +514,7 @@ We create a new channel using the `mpsc::channel` function; `mpsc` stands for *multiple producer, single consumer*. In short, the way Rust’s standard library implements channels means a channel can have multiple *sending* ends that produce values but only one *receiving* end that consumes those values. Imagine -multiple streams flowing together into one big river: everything sent down any +multiple streams flowing together into one big river: Everything sent down any of the streams will end up in one river at the end. We’ll start with a single producer for now, but we’ll add multiple producers when we get this example working. @@ -522,9 +530,9 @@ pattern that destructures the tuples; we’ll discuss the use of patterns in the tuple returned by `mpsc::channel`. Let’s move the transmitting end into a spawned thread and have it send one -string so the spawned thread is communicating with the main thread, as shown in -Listing 16-7. This is like putting a rubber duck in the river upstream or -sending a chat message from one thread to another. +string so that the spawned thread is communicating with the main thread, as +shown in Listing 16-7. This is like putting a rubber duck in the river upstream +or sending a chat message from one thread to another. src/main.rs @@ -545,7 +553,7 @@ fn main() { Listing 16-7: Moving `tx` to a spawned thread and sending `"hi"` Again, we’re using `thread::spawn` to create a new thread and then using `move` -to move `tx` into the closure so the spawned thread owns `tx`. The spawned +to move `tx` into the closure so that the spawned thread owns `tx`. The spawned thread needs to own the transmitter to be able to send messages through the channel. @@ -553,7 +561,7 @@ The transmitter has a `send` method that takes the value we want to send. The `send` method returns a `Result` type, so if the receiver has already been dropped and there’s nowhere to send a value, the send operation will return an error. In this example, we’re calling `unwrap` to panic in case of an -error. But in a real application, we would handle it properly: return to +error. But in a real application, we would handle it properly: Return to Chapter 9 to review strategies for proper error handling. In Listing 16-8, we’ll get the value from the receiver in the main thread. This @@ -590,7 +598,7 @@ an error to signal that no more values will be coming. The `try_recv` method doesn’t block, but will instead return a `Result` immediately: an `Ok` value holding a message if one is available and an `Err` value if there aren’t any messages this time. Using `try_recv` is useful if -this thread has other work to do while waiting for messages: we could write a +this thread has other work to do while waiting for messages: We could write a loop that calls `try_recv` every so often, handles a message if one is available, and otherwise does other work for a little while until checking again. @@ -612,13 +620,17 @@ Got: hi Perfect! -### Channels and Ownership Transference + + + + +### Transferring Ownership Through Channels The ownership rules play a vital role in message sending because they help you write safe, concurrent code. Preventing errors in concurrent programming is the advantage of thinking about ownership throughout your Rust programs. Let’s do an experiment to show how channels and ownership work together to prevent -problems: we’ll try to use a `val` value in the spawned thread *after* we’ve +problems: We’ll try to use a `val` value in the spawned thread *after* we’ve sent it down the channel. Try compiling the code in Listing 16-9 to see why this code isn’t allowed. @@ -645,7 +657,7 @@ fn main() { Listing 16-9: Attempting to use `val` after we’ve sent it down the channel Here, we try to print `val` after we’ve sent it down the channel via `tx.send`. -Allowing this would be a bad idea: once the value has been sent to another +Allowing this would be a bad idea: Once the value has been sent to another thread, that thread could modify or drop it before we try to use the value again. Potentially, the other thread’s modifications could cause errors or unexpected results due to inconsistent or nonexistent data. However, Rust gives @@ -675,13 +687,17 @@ takes ownership of its parameter, and when the value is moved the receiver takes ownership of it. This stops us from accidentally using the value again after sending it; the ownership system checks that everything is okay. -### Sending Multiple Values and Seeing the Receiver Waiting + + + + +### Sending Multiple Values The code in Listing 16-8 compiled and ran, but it didn’t clearly show us that two separate threads were talking to each other over the channel. -In Listing 16-10 we’ve made some modifications that will prove the code in -Listing 16-8 is running concurrently: the spawned thread will now send multiple +In Listing 16-10, we’ve made some modifications that will prove the code in +Listing 16-8 is running concurrently: The spawned thread will now send multiple messages and pause for a second between each message. src/main.rs @@ -722,7 +738,7 @@ between each by calling the `thread::sleep` function with a `Duration` value of one second. In the main thread, we’re not calling the `recv` function explicitly anymore: -instead, we’re treating `rx` as an iterator. For each value received, we’re +Instead, we’re treating `rx` as an iterator. For each value received, we’re printing it. When the channel is closed, iteration will end. When running the code in Listing 16-10, you should see the following output @@ -743,12 +759,16 @@ Because we don’t have any code that pauses or delays in the `for` loop in the main thread, we can tell that the main thread is waiting to receive values from the spawned thread. -### Creating Multiple Producers by Cloning the Transmitter + + + -Earlier we mentioned that `mpsc` was an acronym for *multiple producer, -single consumer*. Let’s put `mpsc` to use and expand the code in Listing 16-10 -to create multiple threads that all send values to the same receiver. We can do -so by cloning the transmitter, as shown in Listing 16-11. +### Creating Multiple Producers + +Earlier we mentioned that `mpsc` was an acronym for *multiple producer, single +consumer*. Let’s put `mpsc` to use and expand the code in Listing 16-10 to +create multiple threads that all send values to the same receiver. We can do so +by cloning the transmitter, as shown in Listing 16-11. src/main.rs @@ -837,7 +857,7 @@ message-passing enthusiasts caution not to use memory sharing? In a way, channels in any programming language are similar to single ownership because once you transfer a value down a channel, you should no longer use that -value. Shared-memory concurrency is like multiple ownership: multiple threads +value. Shared-memory concurrency is like multiple ownership: Multiple threads can access the same memory location at the same time. As you saw in Chapter 15, where smart pointers made multiple ownership possible, multiple ownership can add complexity because these different owners need managing. Rust’s type system @@ -845,7 +865,11 @@ and ownership rules greatly assist in getting this management correct. For an example, let’s look at mutexes, one of the more common concurrency primitives for shared memory. -### Using Mutexes to Allow Access to Data from One Thread at a Time + + + + +### Controlling Access with Mutexes *Mutex* is an abbreviation for *mutual exclusion*, as in a mutex allows only one thread to access some data at any given time. To access the data in a @@ -859,7 +883,7 @@ remember two rules: 1. You must attempt to acquire the lock before using the data. 1. When you’re done with the data that the mutex guards, you must unlock the - data so other threads can acquire the lock. + data so that other threads can acquire the lock. For a real-world metaphor for a mutex, imagine a panel discussion at a conference with only one microphone. Before a panelist can speak, they have to @@ -900,8 +924,8 @@ Listing 16-12: Exploring the API of `Mutex` in a single-threaded context for As with many types, we create a `Mutex` using the associated function `new`. To access the data inside the mutex, we use the `lock` method to acquire the -lock. This call will block the current thread so it can’t do any work until -it’s our turn to have the lock. +lock. This call will block the current thread so that it can’t do any work +until it’s our turn to have the lock. The call to `lock` would fail if another thread holding the lock panicked. In that case, no one would ever be able to get the lock, so we’ve chosen to @@ -925,7 +949,11 @@ used by other threads because the lock release happens automatically. After dropping the lock, we can print the mutex value and see that we were able to change the inner `i32` to `6`. -#### Sharing a Mutex Between Multiple Threads + + + + +#### Shared Access to Mutex Now let’s try to share a value between multiple threads using `Mutex`. We’ll spin up 10 threads and have them each increment a counter value by 1, so the @@ -968,7 +996,7 @@ numbers. We use `thread::spawn` and give all the threads the same closure: one that moves the counter into the thread, acquires a lock on the `Mutex` by calling the `lock` method, and then adds 1 to the value in the mutex. When a thread finishes running its closure, `num` will go out of scope and release the -lock so another thread can acquire it. +lock so that another thread can acquire it. In the main thread, we collect all the join handles. Then, as we did in Listing 16-2, we call `join` on each handle to make sure all the threads finish. At @@ -1014,7 +1042,7 @@ multiple-ownership method we discussed in Chapter 15. #### Multiple Ownership with Multiple Threads In Chapter 15, we gave a value to multiple owners by using the smart pointer -`Rc` to create a reference counted value. Let’s do the same here and see +`Rc` to create a reference-counted value. Let’s do the same here and see what happens. We’ll wrap the `Mutex` in `Rc` in Listing 16-14 and clone the `Rc` before moving ownership to the thread. @@ -1049,8 +1077,8 @@ fn main() { Listing 16-14: Attempting to use `Rc` to allow multiple threads to own the `Mutex` -Once again, we compile and get… different errors! The compiler is teaching us a -lot. +Once again, we compile and get… different errors! The compiler is teaching us +a lot: ``` $ cargo run @@ -1085,8 +1113,8 @@ error: could not compile `shared-state` (bin "shared-state") due to 1 previous e Wow, that error message is very wordy! Here’s the important part to focus on: `` `Rc>` cannot be sent between threads safely ``. The compiler is -also telling us the reason why: `` the trait `Send` is not implemented for `Rc>` ``. We’ll talk about `Send` in the next section: it’s one of -the traits that ensures the types we use with threads are meant for use in +also telling us the reason why: `` the trait `Send` is not implemented for `Rc>` ``. We’ll talk about `Send` in the next section: It’s one of +the traits that ensures that the types we use with threads are meant for use in concurrent situations. Unfortunately, `Rc` is not safe to share across threads. When `Rc` @@ -1103,7 +1131,7 @@ changes to the reference count in a thread-safe way. Fortunately, `Arc` *is* a type like `Rc` that is safe to use in concurrent situations. The *a* stands for *atomic*, meaning it’s an *atomically reference-counted* type. Atomics are an additional kind of concurrency -primitive that we won’t cover in detail here: see the standard library +primitive that we won’t cover in detail here: See the standard library documentation for `std::sync::atomic` for more details. At this point, you just need to know that atomics work like primitive types but are safe to share across threads. @@ -1170,15 +1198,19 @@ Note that if you are doing simple numerical operations, there are types simpler than `Mutex` types provided by the `std::sync::atomic` module of the standard library. These types provide safe, concurrent, atomic access to primitive types. We chose to use `Mutex` with a primitive -type for this example so we could concentrate on how `Mutex` works. +type for this example so that we could concentrate on how `Mutex` works. -### Similarities Between RefCell/Rc and Mutex/Arc + -You might have noticed that `counter` is immutable but we could get a mutable -reference to the value inside it; this means `Mutex` provides interior -mutability, as the `Cell` family does. In the same way we used `RefCell` in -Chapter 15 to allow us to mutate contents inside an `Rc`, we use `Mutex` -to mutate contents inside an `Arc`. + + +### Comparing RefCell/Rc and Mutex/Arc + +You might have noticed that `counter` is immutable but that we could get a +mutable reference to the value inside it; this means `Mutex` provides +interior mutability, as the `Cell` family does. In the same way we used +`RefCell` in Chapter 15 to allow us to mutate contents inside an `Rc`, we +use `Mutex` to mutate contents inside an `Arc`. Another detail to note is that Rust can’t protect you from all kinds of logic errors when you use `Mutex`. Recall from Chapter 15 that using `Rc` came @@ -1187,7 +1219,7 @@ each other, causing memory leaks. Similarly, `Mutex` comes with the risk of creating *deadlocks*. These occur when an operation needs to lock two resources and two threads have each acquired one of the locks, causing them to wait for each other forever. If you’re interested in deadlocks, try creating a Rust -program that has a deadlock; then research deadlock mitigation strategies for +program that has a deadlock; then, research deadlock mitigation strategies for mutexes in any language and have a go at implementing them in Rust. The standard library API documentation for `Mutex` and `MutexGuard` offers useful information. @@ -1195,11 +1227,12 @@ useful information. We’ll round out this chapter by talking about the `Send` and `Sync` traits and how we can use them with custom types. -## Extensible Concurrency with the Send and Sync Traits - - + + + +## Extensible Concurrency with Send and Sync Interestingly, almost every concurrency feature we’ve talked about so far in this chapter has been part of the standard library, not the language. Your @@ -1210,11 +1243,15 @@ written by others. However, among the key concurrency concepts that are embedded in the language rather than the standard library are the `std::marker` traits `Send` and `Sync`. -### Allowing Transference of Ownership Between Threads with Send + + + + +### Transferring Ownership Between Threads The `Send` marker trait indicates that ownership of values of the type implementing `Send` can be transferred between threads. Almost every Rust type -implements `Send`, but there are some exceptions, including `Rc`: this +implements `Send`, but there are some exceptions, including `Rc`: This cannot implement `Send` because if you cloned an `Rc` value and tried to transfer ownership of the clone to another thread, both threads might update the reference count at the same time. For this reason, `Rc` is implemented @@ -1230,7 +1267,11 @@ Any type composed entirely of `Send` types is automatically marked as `Send` as well. Almost all primitive types are `Send`, aside from raw pointers, which we’ll discuss in Chapter 20. -### Allowing Access from Multiple Threads with Sync + + + + +### Accessing from Multiple Threads The `Sync` marker trait indicates that it is safe for the type implementing `Sync` to be referenced from multiple threads. In other words, any type `T` @@ -1244,9 +1285,8 @@ that it doesn’t implement `Send`. The `RefCell` type (which we talked about in Chapter 15) and the family of related `Cell` types don’t implement `Sync`. The implementation of borrow checking that `RefCell` does at runtime is not thread-safe. The smart pointer `Mutex` implements `Sync` and can be -used to share access with multiple threads, as you saw in “Sharing a -`Mutex` Between Multiple -Threads”. +used to share access with multiple threads, as you saw in “Shared Access to +`Mutex`”. ### Implementing Send and Sync Manually Is Unsafe @@ -1265,7 +1305,7 @@ uphold them. ## Summary -This isn’t the last you’ll see of concurrency in this book: the next chapter +This isn’t the last you’ll see of concurrency in this book: The next chapter focuses on async programming, and the project in Chapter 21 will use the concepts in this chapter in a more realistic situation than the smaller examples discussed here. @@ -1283,4 +1323,4 @@ code using these solutions won’t end up with data races or invalid references. Once you get your code to compile, you can rest assured that it will happily run on multiple threads without the kinds of hard-to-track-down bugs common in other languages. Concurrent programming is no longer a concept to be afraid of: -go forth and make your programs concurrent, fearlessly! +Go forth and make your programs concurrent, fearlessly! diff --git a/nostarch/chapter18.md b/nostarch/chapter18.md index f485461a14..64dbe762f6 100644 --- a/nostarch/chapter18.md +++ b/nostarch/chapter18.md @@ -8,6 +8,10 @@ directory, so all fixes need to be made in `/src/`. # Object-Oriented Programming Features + + + + Object-oriented programming (OOP) is a way of modeling programs. Objects as a programmatic concept were introduced in the programming language Simula in the 1960s. Those objects influenced Alan Kay’s programming architecture in which @@ -26,7 +30,7 @@ There is no consensus in the programming community about what features a language must have to be considered object oriented. Rust is influenced by many programming paradigms, including OOP; for example, we explored the features that came from functional programming in Chapter 13. Arguably, OOP languages -share certain common characteristics, namely objects, encapsulation, and +share certain common characteristics—namely, objects, encapsulation, and inheritance. Let’s look at what each of those characteristics means and whether Rust supports it. @@ -41,10 +45,10 @@ object-oriented design patterns. It defines OOP in this way: > data and the procedures that operate on that data. The procedures are > typically called **methods** or **operations**. -Using this definition, Rust is object oriented: structs and enums have data, +Using this definition, Rust is object oriented: Structs and enums have data, and `impl` blocks provide methods on structs and enums. Even though structs and -enums with methods aren’t *called* objects, they provide the same functionality, -according to the Gang of Four’s definition of objects. +enums with methods aren’t *called* objects, they provide the same +functionality, according to the Gang of Four’s definition of objects. ### Encapsulation That Hides Implementation Details @@ -56,7 +60,7 @@ the object’s internals and change data or behavior directly. This enables the programmer to change and refactor an object’s internals without needing to change the code that uses the object. -We discussed how to control encapsulation in Chapter 7: we can use the `pub` +We discussed how to control encapsulation in Chapter 7: We can use the `pub` keyword to decide which modules, types, functions, and methods in our code should be public, and by default everything else is private. For example, we can define a struct `AveragedCollection` that has a field containing a vector @@ -122,7 +126,7 @@ or modify data in an instance of `AveragedCollection`. When an item is added to implementations of each call the private `update_average` method that handles updating the `average` field as well. -We leave the `list` and `average` fields private so there is no way for +We leave the `list` and `average` fields private so that there is no way for external code to add or remove items to or from the `list` field directly; otherwise, the `average` field might become out of sync when the `list` changes. The `average` method returns the value in the `average` field, @@ -157,7 +161,7 @@ can use other solutions in Rust, depending on your reason for reaching for inheritance in the first place. You would choose inheritance for two main reasons. One is for reuse of code: -you can implement particular behavior for one type, and inheritance enables you +You can implement particular behavior for one type, and inheritance enables you to reuse that implementation for a different type. You can do this in a limited way in Rust code using default trait method implementations, which you saw in Listing 10-14 when we added a default implementation of the `summarize` method @@ -184,7 +188,7 @@ each other at runtime if they share certain characteristics. > trait bounds to impose constraints on what those types must provide. This is > sometimes called *bounded parametric polymorphism*. -Rust has chosen a different set of tradeoffs by not offering inheritance. +Rust has chosen a different set of trade-offs by not offering inheritance. Inheritance is often at risk of sharing more code than necessary. Subclasses shouldn’t always share all characteristics of their parent class but will do so with inheritance. This can make a program’s design less flexible. It also @@ -195,8 +199,8 @@ subclass can only inherit from one class), further restricting the flexibility of a program’s design. For these reasons, Rust takes the different approach of using trait objects -instead of inheritance to enable polymorphism. Let’s look at how trait objects -work. +instead of inheritance to achieve polymorphism at runtime. Let’s look at how +trait objects work. @@ -219,8 +223,8 @@ through a list of items, calling a `draw` method on each one to draw it to the screen—a common technique for GUI tools. We’ll create a library crate called `gui` that contains the structure of a GUI library. This crate might include some types for people to use, such as `Button` or `TextField`. In addition, -`gui` users will want to create their own types that can be drawn: for -instance, one programmer might add an `Image` and another might add a +`gui` users will want to create their own types that can be drawn: For +instance, one programmer might add an `Image`, and another might add a `SelectBox`. At the time of writing the library, we can’t know and define all the types @@ -241,19 +245,19 @@ allow users to create new types compatible with the library. ### Defining a Trait for Common Behavior -To implement the behavior we want `gui` to have, we’ll define a trait named -`Draw` that will have one method named `draw`. Then we can define a vector that -takes a trait object. A *trait object* points to both an instance of a type -implementing our specified trait and a table used to look up trait methods on -that type at runtime. We create a trait object by specifying some sort of -pointer, such as an `&` reference or a `Box` smart pointer, then the `dyn` -keyword, and then specifying the relevant trait. (We’ll talk about the reason -trait objects must use a pointer in “Dynamically Sized Types and the `Sized` -Trait” in Chapter 20.) We can use trait -objects in place of a generic or concrete type. Wherever we use a trait object, -Rust’s type system will ensure at compile time that any value used in that -context will implement the trait object’s trait. Consequently, we don’t need to -know all the possible types at compile time. +To implement the behavior that we want `gui` to have, we’ll define a trait +named `Draw` that will have one method named `draw`. Then, we can define a +vector that takes a trait object. A *trait object* points to both an instance +of a type implementing our specified trait and a table used to look up trait +methods on that type at runtime. We create a trait object by specifying some +sort of pointer, such as a reference or a `Box` smart pointer, then the +`dyn` keyword, and then specifying the relevant trait. (We’ll talk about the +reason trait objects must use a pointer in “Dynamically Sized Types and the +`Sized` Trait” in Chapter 20.) We can use +trait objects in place of a generic or concrete type. Wherever we use a trait +object, Rust’s type system will ensure at compile time that any value used in +that context will implement the trait object’s trait. Consequently, we don’t +need to know all the possible types at compile time. We’ve mentioned that, in Rust, we refrain from calling structs and enums “objects” to distinguish them from other languages’ objects. In a struct or @@ -261,7 +265,7 @@ enum, the data in the struct fields and the behavior in `impl` blocks are separated, whereas in other languages, the data and behavior combined into one concept is often labeled an object. Trait objects differ from objects in other languages in that we can’t add data to a trait object. Trait objects aren’t as -generally useful as objects in other languages: their specific purpose is to +generally useful as objects in other languages: Their specific purpose is to allow abstraction across common behavior. Listing 18-3 shows how to define a trait named `Draw` with one method named @@ -336,8 +340,7 @@ where } ``` -Listing 18-6: An alternate implementation of the `Screen` struct and its `run` -method using generics and trait bounds +Listing 18-6: An alternate implementation of the `Screen` struct and its `run` method using generics and trait bounds This restricts us to a `Screen` instance that has a list of components all of type `Button` or all of type `TextField`. If you’ll only ever have homogeneous @@ -453,7 +456,7 @@ means it implements the `draw` method. This concept—of being concerned only with the messages a value responds to rather than the value’s concrete type—is similar to the concept of *duck -typing* in dynamically typed languages: if it walks like a duck and quacks like +typing* in dynamically typed languages: If it walks like a duck and quacks like a duck, then it must be a duck! In the implementation of `run` on `Screen` in Listing 18-5, `run` doesn’t need to know what the concrete type of each component is. It doesn’t check whether a component is an instance of a `Button` @@ -509,12 +512,16 @@ This error lets us know that either we’re passing something to `Screen` that w didn’t mean to pass and so should pass a different type, or we should implement `Draw` on `String` so that `Screen` is able to call `draw` on it. -### Trait Objects Perform Dynamic Dispatch + + + + +### Performing Dynamic Dispatch Recall in “Performance of Code Using Generics” in Chapter 10 our discussion on the monomorphization process performed on generics by the -compiler: the compiler generates nongeneric implementations of functions and +compiler: The compiler generates nongeneric implementations of functions and methods for each concrete type that we use in place of a generic type parameter. The code that results from monomorphization is doing *static dispatch*, which is when the compiler knows what method you’re calling at @@ -526,13 +533,14 @@ When we use trait objects, Rust must use dynamic dispatch. The compiler doesn’ know all the types that might be used with the code that’s using trait objects, so it doesn’t know which method implemented on which type to call. Instead, at runtime, Rust uses the pointers inside the trait object to know which method to -call. This lookup incurs a runtime cost that doesn’t occur with static -dispatch. Dynamic dispatch also prevents the compiler from choosing to inline a -method’s code, which in turn prevents some optimizations, and Rust has some -rules about where you can and cannot use dynamic dispatch, called *dyn -compatibility* at *https://doc.rust-lang.org/reference/items/traits.html#dyn-compatibility*. However, we did get extra flexibility in the code -that we wrote in Listing 18-5 and were able to support in Listing 18-9, so it’s -a trade-off to consider. +call. This lookup incurs a runtime cost that doesn’t occur with static dispatch. +Dynamic dispatch also prevents the compiler from choosing to inline a method’s +code, which in turn prevents some optimizations, and Rust has some rules about +where you can and cannot use dynamic dispatch, called *dyn compatibility*. Those +rules are beyond the scope of this discussion, but you can read more about them +in the reference. However, we did get extra +flexibility in the code that we wrote in Listing 18-5 and were able to support +in Listing 18-9, so it’s a trade-off to consider. ## Implementing an Object-Oriented Design Pattern @@ -543,7 +551,7 @@ changes based on its state. We’re going to work through an example of a blog post struct that has a field to hold its state, which will be a state object from the set “draft,” “review,” or “published.” -The state objects share functionality: in Rust, of course, we use structs and +The state objects share functionality: In Rust, of course, we use structs and traits rather than objects and inheritance. Each state object is responsible for its own behavior and for governing when it should change into another state. The value that holds a state object knows nothing about the different @@ -555,8 +563,8 @@ value holding the state or the code that uses the value. We’ll only need to update the code inside one of the state objects to change its rules or perhaps add more state objects. -First we’re going to implement the state pattern in a more traditional -object-oriented way, then we’ll use an approach that’s a bit more natural in +First, we’re going to implement the state pattern in a more traditional +object-oriented way. Then, we’ll use an approach that’s a bit more natural in Rust. Let’s dig in to incrementally implement a blog post workflow using the state pattern. @@ -565,14 +573,18 @@ The final functionality will look like this: 1. A blog post starts as an empty draft. 1. When the draft is done, a review of the post is requested. 1. When the post is approved, it gets published. -1. Only published blog posts return content to print, so unapproved posts can’t - accidentally be published. +1. Only published blog posts return content to print so that unapproved posts + can’t accidentally be published. Any other changes attempted on a post should have no effect. For example, if we try to approve a draft blog post before we’ve requested a review, the post should remain an unpublished draft. -### A Traditional Object-oriented Attempt + + + + +### Attempting Traditional Object-Oriented Style There are infinite ways to structure code to solve the same problem, each with different trade-offs. This section’s implementation is more of a traditional @@ -583,7 +595,7 @@ in a way that might look less familiar to programmers with object-oriented experience. We’ll compare the two solutions to experience the trade-offs of designing Rust code differently than code in other languages. -Listing 18-11 shows this workflow in code form: this is an example usage of the +Listing 18-11 shows this workflow in code form: This is an example usage of the API we’ll implement in a library crate named `blog`. This won’t compile yet because we haven’t implemented the `blog` crate. @@ -630,7 +642,11 @@ methods called by our library’s users on the `Post` instance, but they don’t have to manage the state changes directly. Also, users can’t make a mistake with the states, such as publishing a post before it’s reviewed. -#### Defining Post and Creating a New Instance in the Draft State + + + + +#### Defining Post and Creating a New Instance Let’s get started on the implementation of the library! We know we need a public `Post` struct that holds some content, so we’ll start with the @@ -639,7 +655,7 @@ instance of `Post`, as shown in Listing 18-12. We’ll also make a private `State` trait that will define the behavior that all state objects for a `Post` must have. -Then `Post` will hold a trait object of `Box` inside an `Option` +Then, `Post` will hold a trait object of `Box` inside an `Option` in a private field named `state` to hold the state object. You’ll see why the `Option` is necessary in a bit. @@ -689,8 +705,7 @@ We saw in Listing 18-11 that we want to be able to call a method named blog post. We implement this as a method, rather than exposing the `content` field as `pub`, so that later we can implement a method that will control how the `content` field’s data is read. The `add_text` method is pretty -straightforward, so let’s add the implementation in Listing 18-13 to the `impl -Post` block. +straightforward, so let’s add the implementation in Listing 18-13 to the `impl Post` block. src/lib.rs @@ -713,16 +728,21 @@ so it’s not part of the state pattern. The `add_text` method doesn’t interac with the `state` field at all, but it is part of the behavior we want to support. -#### Ensuring the Content of a Draft Post Is Empty + + + + +#### Ensuring That the Content of a Draft Post Is Empty Even after we’ve called `add_text` and added some content to our post, we still want the `content` method to return an empty string slice because the post is -still in the draft state, as shown on line 7 of Listing 18-11. For now, let’s -implement the `content` method with the simplest thing that will fulfill this -requirement: always returning an empty string slice. We’ll change this later -once we implement the ability to change a post’s state so it can be published. -So far, posts can only be in the draft state, so the post content should always -be empty. Listing 18-14 shows this placeholder implementation. +still in the draft state, as shown by the first `assert_eq!` in Listing 18-11. +For now, let’s implement the `content` method with the simplest thing that will +fulfill this requirement: always returning an empty string slice. We’ll change +this later once we implement the ability to change a post’s state so that it +can be published. So far, posts can only be in the draft state, so the post +content should always be empty. Listing 18-14 shows this placeholder +implementation. src/lib.rs @@ -737,14 +757,15 @@ impl Post { Listing 18-14: Adding a placeholder implementation for the `content` method on `Post` that always returns an empty string slice -With this added `content` method, everything in Listing 18-11 up to line 7 -works as intended. +With this added `content` method, everything in Listing 18-11 through the first +`assert_eq!` works as intended. + -#### Requesting a Review Changes the Post’s State +#### Requesting a Review, Which Changes the Post’s State Next, we need to add functionality to request a review of a post, which should change its state from `Draft` to `PendingReview`. Listing 18-15 shows this code. @@ -785,7 +806,7 @@ impl State for PendingReview { Listing 18-15: Implementing `request_review` methods on `Post` and the `State` trait We give `Post` a public method named `request_review` that will take a mutable -reference to `self`. Then we call an internal `request_review` method on the +reference to `self`. Then, we call an internal `request_review` method on the current state of `Post`, and this second `request_review` method consumes the current state and returns a new state. @@ -794,21 +815,21 @@ implement the trait will now need to implement the `request_review` method. Note that rather than having `self`, `&self`, or `&mut self` as the first parameter of the method, we have `self: Box`. This syntax means the method is only valid when called on a `Box` holding the type. This syntax takes -ownership of `Box`, invalidating the old state so the state value of the -`Post` can transform into a new state. +ownership of `Box`, invalidating the old state so that the state value of +the `Post` can transform into a new state. To consume the old state, the `request_review` method needs to take ownership of the state value. This is where the `Option` in the `state` field of `Post` -comes in: we call the `take` method to take the `Some` value out of the `state` +comes in: We call the `take` method to take the `Some` value out of the `state` field and leave a `None` in its place because Rust doesn’t let us have unpopulated fields in structs. This lets us move the `state` value out of -`Post` rather than borrowing it. Then we’ll set the post’s `state` value to the -result of this operation. +`Post` rather than borrowing it. Then, we’ll set the post’s `state` value to +the result of this operation. We need to set `state` to `None` temporarily rather than setting it directly with code like `self.state = self.state.request_review();` to get ownership of -the `state` value. This ensures `Post` can’t use the old `state` value after -we’ve transformed it into a new state. +the `state` value. This ensures that `Post` can’t use the old `state` value +after we’ve transformed it into a new state. The `request_review` method on `Draft` returns a new, boxed instance of a new `PendingReview` struct, which represents the state when a post is waiting for a @@ -817,22 +838,23 @@ but doesn’t do any transformations. Rather, it returns itself because when we request a review on a post already in the `PendingReview` state, it should stay in the `PendingReview` state. -Now we can start seeing the advantages of the state pattern: the +Now we can start seeing the advantages of the state pattern: The `request_review` method on `Post` is the same no matter its `state` value. Each state is responsible for its own rules. We’ll leave the `content` method on `Post` as is, returning an empty string slice. We can now have a `Post` in the `PendingReview` state as well as in the `Draft` state, but we want the same behavior in the `PendingReview` state. -Listing 18-11 now works up to line 10! +Listing 18-11 now works up to the second `assert_eq!` call! + -#### Adding `approve` to Change the Behavior of content +#### Adding approve to Change content’s Behavior -The `approve` method will be similar to the `request_review` method: it will +The `approve` method will be similar to the `request_review` method: It will set `state` to the value that the current state says it should have when that state is approved, as shown in Listing 18-16. @@ -918,25 +940,24 @@ Listing 18-17: Updating the `content` method on `Post` to delegate to a `content Because the goal is to keep all of these rules inside the structs that implement `State`, we call a `content` method on the value in `state` and pass -the post instance (that is, `self`) as an argument. Then we return the value +the post instance (that is, `self`) as an argument. Then, we return the value that’s returned from using the `content` method on the `state` value. We call the `as_ref` method on the `Option` because we want a reference to the value inside the `Option` rather than ownership of the value. Because `state` is -an `Option>`, when we call `as_ref`, an `Option<&Box>` is returned. If we didn’t call `as_ref`, we would get an error because +an `Option>`, when we call `as_ref`, an `Option<&Box>` is returned. If we didn’t call `as_ref`, we would get an error because we can’t move `state` out of the borrowed `&self` of the function parameter. -We then call the `unwrap` method, which we know will never panic because we know -the methods on `Post` ensure that `state` will always contain a `Some` value when -those methods are done. This is one of the cases we talked about in “Cases in -Which You Have More Information Than the +We then call the `unwrap` method, which we know will never panic because we +know the methods on `Post` ensure that `state` will always contain a `Some` +value when those methods are done. This is one of the cases we talked about in +the “When You Have More Information Than the Compiler” section of Chapter 9 when we know that a `None` value is never possible, even though the compiler isn’t able to understand that. At this point, when we call `content` on the `&Box`, deref coercion -will take effect on the `&` and the `Box` so the `content` method will +will take effect on the `&` and the `Box` so that the `content` method will ultimately be called on the type that implements the `State` trait. That means we need to add `content` to the `State` trait definition, and that is where we’ll put the logic for what content to return depending on which state we @@ -969,7 +990,7 @@ We add a default implementation for the `content` method that returns an empty string slice. That means we don’t need to implement `content` on the `Draft` and `PendingReview` structs. The `Published` struct will override the `content` method and return the value in `post.content`. While convenient, having the -`content` method on `State` determine the `content` of the `Post` is blurring +`content` method on `State` determine the content of the `Post` is blurring the lines between the responsibility of `State` and the responsibility of `Post`. @@ -984,21 +1005,25 @@ rules lives in the state objects rather than being scattered throughout `Post`. > ### Why Not An Enum? > -> You may have been wondering why we didn’t use an `enum` with the different +> You may have been wondering why we didn’t use an enum with the different > possible post states as variants. That’s certainly a possible solution; try it > and compare the end results to see which you prefer! One disadvantage of using > an enum is that every place that checks the value of the enum will need a > `match` expression or similar to handle every possible variant. This could get > more repetitive than this trait object solution. -#### Trade-offs of the State Pattern + + + + +#### Evaluating the State Pattern We’ve shown that Rust is capable of implementing the object-oriented state pattern to encapsulate the different kinds of behavior a post should have in -each state. The methods on `Post` know nothing about the various behaviors. The -way we organized the code, we have to look in only one place to know the -different ways a published post can behave: the implementation of the `State` -trait on the `Published` struct. +each state. The methods on `Post` know nothing about the various behaviors. +Because of the way we organized the code, we have to look in only one place to +know the different ways a published post can behave: the implementation of the +`State` trait on the `Published` struct. If we were to create an alternative implementation that didn’t use the state pattern, we might instead use `match` expressions in the methods on `Post` or @@ -1032,17 +1057,18 @@ another design pattern. Another downside is that we’ve duplicated some logic. To eliminate some of the duplication, we might try to make default implementations for the `request_review` and `approve` methods on the `State` trait that return `self`. -However, this wouldn’t work: when using `State` as a trait object, the trait +However, this wouldn’t work: When using `State` as a trait object, the trait doesn’t know what the concrete `self` will be exactly, so the return type isn’t -known at compile time. (This is one of the `dyn` compatibility rules mentioned +known at compile time. (This is one of the dyn compatibility rules mentioned earlier.) Other duplication includes the similar implementations of the `request_review` -and `approve` methods on `Post`. Both methods delegate to the implementation of -the same method on the value in the `state` field of `Option` and set the new -value of the `state` field to the result. If we had a lot of methods on `Post` -that followed this pattern, we might consider defining a macro to eliminate the -repetition (see the “Macros” section in Chapter 20). +and `approve` methods on `Post`. Both methods use `Option::take` with the +`state` field of `Post`, and if `state` is `Some`, they delegate to the wrapped +value’s implementation of the same method and set the new value of the `state` +field to the result. If we had a lot of methods on `Post` that followed this +pattern, we might consider defining a macro to eliminate the repetition (see +the “Macros” section in Chapter 20). By implementing the state pattern exactly as it’s defined for object-oriented languages, we’re not taking as full advantage of Rust’s strengths as we could. @@ -1053,9 +1079,10 @@ invalid states and transitions into compile-time errors. We’ll show you how to rethink the state pattern to get a different set of trade-offs. Rather than encapsulating the states and transitions completely so -outside code has no knowledge of them, we’ll encode the states into different -types. Consequently, Rust’s type checking system will prevent attempts to use -draft posts where only published posts are allowed by issuing a compiler error. +that outside code has no knowledge of them, we’ll encode the states into +different types. Consequently, Rust’s type-checking system will prevent +attempts to use draft posts where only published posts are allowed by issuing a +compiler error. Let’s consider the first part of `main` in Listing 18-11: @@ -1070,11 +1097,13 @@ fn main() { } ``` + + We still enable the creation of new posts in the draft state using `Post::new` and the ability to add text to the post’s content. But instead of having a `content` method on a draft post that returns an empty string, we’ll make it so -draft posts don’t have the `content` method at all. That way, if we try to get -a draft post’s content, we’ll get a compiler error telling us the method +that draft posts don’t have the `content` method at all. That way, if we try to +get a draft post’s content, we’ll get a compiler error telling us the method doesn’t exist. As a result, it will be impossible for us to accidentally display draft post content in production because that code won’t even compile. Listing 18-19 shows the definition of a `Post` struct and a `DraftPost` struct, @@ -1125,15 +1154,15 @@ instance of `Post` right now. The `DraftPost` struct has an `add_text` method, so we can add text to `content` as before, but note that `DraftPost` does not have a `content` method -defined! So now the program ensures all posts start as draft posts, and draft -posts don’t have their content available for display. Any attempt to get around -these constraints will result in a compiler error. +defined! So now the program ensures that all posts start as draft posts, and +draft posts don’t have their content available for display. Any attempt to get +around these constraints will result in a compiler error. -So how do we get a published post? We want to enforce the rule that a draft +So, how do we get a published post? We want to enforce the rule that a draft post has to be reviewed and approved before it can be published. A post in the pending review state should still not display any content. Let’s implement these constraints by adding another struct, `PendingReviewPost`, defining the @@ -1184,7 +1213,7 @@ But we also have to make some small changes to `main`. The `request_review` and `approve` methods return new instances rather than modifying the struct they’re called on, so we need to add more `let post =` shadowing assignments to save the returned instances. We also can’t have the assertions about the draft and -pending review posts’ contents be empty strings, nor do we need them: we can’t +pending review posts’ contents be empty strings, nor do we need them: We can’t compile code that tries to use the content of posts in those states any longer. The updated code in `main` is shown in Listing 18-21. @@ -1210,7 +1239,7 @@ Listing 18-21: Modifications to `main` to use the new implementation of the blog The changes we needed to make to `main` to reassign `post` mean that this implementation doesn’t quite follow the object-oriented state pattern anymore: -the transformations between the states are no longer encapsulated entirely +The transformations between the states are no longer encapsulated entirely within the `Post` implementation. However, our gain is that invalid states are now impossible because of the type system and the type checking that happens at compile time! This ensures that certain bugs, such as display of the content of diff --git a/nostarch/chapter19.md b/nostarch/chapter19.md index 12947f08bf..b7df52a9b4 100644 --- a/nostarch/chapter19.md +++ b/nostarch/chapter19.md @@ -8,7 +8,7 @@ directory, so all fixes need to be made in `/src/`. # Patterns and Matching -*Patterns* are a special syntax in Rust for matching against the structure of +Patterns are a special syntax in Rust for matching against the structure of types, both complex and simple. Using patterns in conjunction with `match` expressions and other constructs gives you more control over a program’s control flow. A pattern consists of some combination of the following: @@ -70,19 +70,19 @@ match x { } ``` -The patterns in this `match` expression are the `None` and `Some(i)` on the +The patterns in this `match` expression are the `None` and `Some(i)` to the left of each arrow. -One requirement for `match` expressions is that they need to be *exhaustive* in +One requirement for `match` expressions is that they need to be exhaustive in the sense that all possibilities for the value in the `match` expression must -be accounted for. One way to ensure you’ve covered every possibility is to have -a catch-all pattern for the last arm: for example, a variable name matching any -value can never fail and thus covers every remaining case. +be accounted for. One way to ensure that you’ve covered every possibility is to +have a catch-all pattern for the last arm: For example, a variable name +matching any value can never fail and thus covers every remaining case. The particular pattern `_` will match anything, but it never binds to a variable, so it’s often used in the last match arm. The `_` pattern can be -useful when you want to ignore any value not specified, for example. We’ll cover -the `_` pattern in more detail in “Ignoring Values in a +useful when you want to ignore any value not specified, for example. We’ll +cover the `_` pattern in more detail in “Ignoring Values in a Pattern” later in this chapter. ### let Statements @@ -127,8 +127,8 @@ To see the pattern-matching aspect of `let` more clearly, consider Listing Listing 19-1: Using a pattern to destructure a tuple and create three variables at once Here, we match a tuple against a pattern. Rust compares the value `(1, 2, 3)` -to the pattern `(x, y, z)` and sees that the value matches the pattern, in that -it sees that the number of elements is the same in both, so Rust binds `1` to +to the pattern `(x, y, z)` and sees that the value matches the pattern—that is, +it sees that the number of elements is the same in both—so Rust binds `1` to `x`, `2` to `y`, and `3` to `z`. You can think of this tuple pattern as nesting three individual variable patterns inside it. @@ -168,8 +168,8 @@ To fix the error, we could ignore one or more of the values in the tuple using `_` or `..`, as you’ll see in the “Ignoring Values in a Pattern” section. If the problem is that we have too many variables in the pattern, the solution is to make the -types match by removing variables so the number of variables equals the number -of elements in the tuple. +types match by removing variables so that the number of variables equals the +number of elements in the tuple. ### Conditional if let Expressions @@ -224,9 +224,9 @@ This conditional structure lets us support complex requirements. With the hardcoded values we have here, this example will print `Using purple as the background color`. You can see that `if let` can also introduce new variables that shadow existing -variables in the same way that `match` arms can: the line `if let Ok(age) = age` +variables in the same way that `match` arms can: The line `if let Ok(age) = age` introduces a new `age` variable that contains the value inside the `Ok` variant, -shadowing the existing `age` variable. This means we need to place the `if age > 30` condition within that block: we can’t combine these two conditions into `if let Ok(age) = age && age > 30`. The new `age` we want to compare to 30 isn’t +shadowing the existing `age` variable. This means we need to place the `if age > 30` condition within that block: We can’t combine these two conditions into `if let Ok(age) = age && age > 30`. The new `age` we want to compare to 30 isn’t valid until the new scope starts with the curly bracket. The downside of using `if let` expressions is that the compiler doesn’t check @@ -238,7 +238,7 @@ not alert us to the possible logic bug. Similar in construction to `if let`, the `while let` conditional loop allows a `while` loop to run for as long as a pattern continues to match. In Listing -19-4 we show a `while let` loop that waits on messages sent between threads, +19-4, we show a `while let` loop that waits on messages sent between threads, but in this case checking a `Result` instead of an `Option`. @@ -260,16 +260,16 @@ Listing 19-4: Using a `while let` loop to print values for as long as `rx.recv() This example prints `1`, `2`, and then `3`. The `recv` method takes the first message out of the receiver side of the channel and returns an `Ok(value)`. When we first saw `recv` back in Chapter 16, we unwrapped the error directly, or -interacted with it as an iterator using a `for` loop. As Listing 19-4 shows, -though, we can also use while let, because the `recv` method returns an `Ok` +we interacted with it as an iterator using a `for` loop. As Listing 19-4 shows, +though, we can also use `while let`, because the `recv` method returns an `Ok` each time a message arrives, as long as the sender exists, and then produces an -`Err `once the sender side disconnects. +`Err` once the sender side disconnects. ### for Loops In a `for` loop, the value that directly follows the keyword `for` is a pattern. For example, in `for x in y`, the `x` is the pattern. Listing 19-5 -demonstrates how to use a pattern in a `for` loop to *destructure*, or break +demonstrates how to use a pattern in a `for` loop to destructure, or break apart, a tuple as part of the `for` loop. @@ -295,11 +295,10 @@ b is at index 1 c is at index 2 ``` -We adapt an iterator using the `enumerate` method so it produces a value and -the index for that value, placed into a tuple. The first value produced is the -tuple `(0, 'a')`. When this value is matched to the pattern `(index, value)`, -`index` will be `0` and `value` will be `'a'`, printing the first line of the -output. +We adapt an iterator using the `enumerate` method so that it produces a value +and the index for that value, placed into a tuple. The first value produced is +the tuple `(0, 'a')`. When this value is matched to the pattern `(index, value)`, index will be `0` and value will be `'a'`, printing the first line of +the output. ### Function Parameters @@ -314,7 +313,7 @@ fn foo(x: i32) { } ``` -Listing 19-6: A function signature uses patterns in the parameters +Listing 19-6: A function signature using patterns in the parameters The `x` part is a pattern! As we did with `let`, we could match a tuple in a function’s arguments to the pattern. Listing 19-7 splits the values in a tuple @@ -361,12 +360,12 @@ irrefutable patterns because the program cannot do anything meaningful when values don’t match. The `if let` and `while let` expressions and the `let...else` statement accept refutable and irrefutable patterns, but the compiler warns against irrefutable patterns because, by definition, they’re -intended to handle possible failure: the functionality of a conditional is in +intended to handle possible failure: The functionality of a conditional is in its ability to perform differently depending on success or failure. In general, you shouldn’t have to worry about the distinction between refutable and irrefutable patterns; however, you do need to be familiar with the concept -of refutability so you can respond when you see it in an error message. In +of refutability so that you can respond when you see it in an error message. In those cases, you’ll need to change either the pattern or the construct you’re using the pattern with, depending on the intended behavior of the code. @@ -413,7 +412,7 @@ Because we didn’t cover (and couldn’t cover!) every valid value with the pattern `Some(x)`, Rust rightfully produces a compiler error. If we have a refutable pattern where an irrefutable pattern is needed, we can -fix it by changing the code that uses the pattern: instead of using `let`, we +fix it by changing the code that uses the pattern: Instead of using `let`, we can use `let else`. Then, if the pattern doesn’t match, the code will just skip the code in the curly brackets, giving it a way to continue validly. Listing 19-9 shows how to fix the code in Listing 19-8. @@ -551,13 +550,17 @@ the inner `y`. The last `println!` produces `at the end: x = Some(5), y = 10`. To create a `match` expression that compares the values of the outer `x` and `y`, rather than introducing a new variable that shadows the existing `y` variable, we would need to use a match guard conditional instead. We’ll talk -about match guards later in “Extra Conditionals with Match -Guards”. +about match guards later in the “Adding Conditionals with Match +Guards” section. -### Multiple Patterns + + + + +### Matching Multiple Patterns In `match` expressions, you can match multiple patterns using the `|` syntax, -which is the pattern *or* operator. For example, in the following code we match +which is the pattern *or* operator. For example, in the following code, we match the value of `x` against the match arms, the first of which has an *or* option, meaning if the value of `x` matches either of the values in that arm, that arm’s code will run: @@ -617,7 +620,11 @@ Rust can tell that `'c'` is within the first pattern’s range and prints `early We can also use patterns to destructure structs, enums, and tuples to use different parts of these values. Let’s walk through each value. -#### Destructuring Structs + + + + +#### Structs Listing 19-12 shows a `Point` struct with two fields, `x` and `y`, that we can break apart using a pattern with a `let` statement. @@ -648,7 +655,7 @@ However, it’s common to match the variable names to the field names to make it easier to remember which variables came from which fields. Because of this common usage, and because writing `let Point { x: x, y: y } = p;` contains a lot of duplication, Rust has a shorthand for patterns that match struct fields: -you only need to list the name of the struct field, and the variables created +You only need to list the name of the struct field, and the variables created from the pattern will have the same names. Listing 19-13 behaves in the same way as the code in Listing 19-12, but the variables created in the `let` pattern are `x` and `y` instead of `a` and `b`. @@ -719,12 +726,16 @@ Remember that a `match` expression stops checking arms once it has found the first matching pattern, so even though `Point { x: 0, y: 0}` is on the `x` axis and the `y` axis, this code would only print `On the x axis at 0`. -#### Destructuring Enums + + + + +#### Enums We’ve destructured enums in this book (for example, Listing 6-5 in Chapter 6), -but haven’t yet explicitly discussed that the pattern to destructure an enum +but we haven’t yet explicitly discussed that the pattern to destructure an enum corresponds to the way the data stored within the enum is defined. As an -example, in Listing 19-15 we use the `Message` enum from Listing 6-2 and write +example, in Listing 19-15, we use the `Message` enum from Listing 6-2 and write a `match` with patterns that will destructure each inner value. src/main.rs @@ -768,9 +779,9 @@ and no variables are in that pattern. For struct-like enum variants, such as `Message::Move`, we can use a pattern similar to the pattern we specify to match structs. After the variant name, we -place curly brackets and then list the fields with variables so we break apart -the pieces to use in the code for this arm. Here we use the shorthand form as -we did in Listing 19-13. +place curly brackets and then list the fields with variables so that we break +apart the pieces to use in the code for this arm. Here we use the shorthand +form as we did in Listing 19-13. For tuple-like enum variants, like `Message::Write` that holds a tuple with one element and `Message::ChangeColor` that holds a tuple with three elements, the @@ -778,7 +789,11 @@ pattern is similar to the pattern we specify to match tuples. The number of variables in the pattern must match the number of elements in the variant we’re matching. -#### Destructuring Nested Structs and Enums + + + + +#### Nested Structs and Enums So far, our examples have all been matching structs or enums one level deep, but matching can work on nested items too! For example, we can refactor the @@ -817,13 +832,17 @@ fn main() { Listing 19-16: Matching on nested enums The pattern of the first arm in the `match` expression matches a -`Message::ChangeColor` enum variant that contains a `Color::Rgb` variant; then +`Message::ChangeColor` enum variant that contains a `Color::Rgb` variant; then, the pattern binds to the three inner `i32` values. The pattern of the second arm also matches a `Message::ChangeColor` enum variant, but the inner enum matches `Color::Hsv` instead. We can specify these complex conditions in one `match` expression, even though two enums are involved. -#### Destructuring Structs and Tuples + + + + +#### Structs and Tuples We can mix, match, and nest destructuring patterns in even more complex ways. The following example shows a complicated destructure where we nest structs and @@ -833,8 +852,8 @@ tuples inside a tuple and destructure all the primitive values out: let ((feet, inches), Point { x, y }) = ((3, 10), Point { x: 3, y: -10 }); ``` -This code lets us break complex types into their component parts so we can use -the values we’re interested in separately. +This code lets us break complex types into their component parts so that we can +use the values we’re interested in separately. Destructuring with patterns is a convenient way to use pieces of values, such as the value from each field in a struct, separately from each other. @@ -849,7 +868,7 @@ pattern (which you’ve seen), using the `_` pattern within another pattern, using a name that starts with an underscore, or using `..` to ignore remaining parts of a value. Let’s explore how and why to use each of these patterns. - + @@ -878,12 +897,14 @@ This code will completely ignore the value `3` passed as the first argument, and will print `This code only uses the y parameter: 4`. In most cases when you no longer need a particular function parameter, you -would change the signature so it doesn’t include the unused parameter. Ignoring -a function parameter can be especially useful in cases when, for example, -you’re implementing a trait when you need a certain type signature but the -function body in your implementation doesn’t need one of the parameters. You -then avoid getting a compiler warning about unused function parameters, as you -would if you used a name instead. +would change the signature so that it doesn’t include the unused parameter. +Ignoring a function parameter can be especially useful in cases when, for +example, you’re implementing a trait when you need a certain type signature but +the function body in your implementation doesn’t need one of the parameters. +You then avoid getting a compiler warning about unused function parameters, as +you would if you used a name instead. + + @@ -946,7 +967,7 @@ Listing 19-19: Ignoring multiple parts of a tuple This code will print `Some numbers: 2, 8, 32`, and the values `4` and `16` will be ignored. - + @@ -1113,7 +1134,11 @@ ignore thereafter. This code could mean that we want to ignore `2`, bind The variable name `second` doesn’t mean anything special to Rust, so we get a compiler error because using `..` in two places like this is ambiguous. -### Extra Conditionals with Match Guards + + + + +### Adding Conditionals with Match Guards A *match guard* is an additional `if` condition, specified after the pattern in a `match` arm, that must also match for that arm to be chosen. Match guards are @@ -1139,7 +1164,7 @@ guard of `if x % 2 == 0` (which will be `true` if the number is even). Listing 19-26: Adding a match guard to a pattern This example will print `The number 4 is even`. When `num` is compared to the -pattern in the first arm, it matches because `Some(4)` matches `Some(x)`. Then +pattern in the first arm, it matches because `Some(4)` matches `Some(x)`. Then, the match guard checks whether the remainder of dividing `x` by 2 is equal to 0, and because it is, the first arm is selected. @@ -1153,12 +1178,12 @@ the match guard gives us the ability to express this logic. The downside of this additional expressiveness is that the compiler doesn’t try to check for exhaustiveness when match guard expressions are involved. -In Listing 19-11, we mentioned that we could use match guards to solve our -pattern-shadowing problem. Recall that we created a new variable inside the -pattern in the `match` expression instead of using the variable outside the -`match`. That new variable meant we couldn’t test against the value of the -outer variable. Listing 19-27 shows how we can use a match guard to fix this -problem. +When discussing Listing 19-11, we mentioned that we could use match guards to +solve our pattern-shadowing problem. Recall that we created a new variable +inside the pattern in the `match` expression instead of using the variable +outside the `match`. That new variable meant we couldn’t test against the value +of the outer variable. Listing 19-27 shows how we can use a match guard to fix +this problem. src/main.rs @@ -1230,17 +1255,21 @@ rather than this: 4 | 5 | (6 if y) => ... ``` -After running the code, the precedence behavior is evident: if the match guard +After running the code, the precedence behavior is evident: If the match guard were applied only to the final value in the list of values specified using the -`|` operator, the arm would have matched and the program would have printed +`|` operator, the arm would have matched, and the program would have printed `yes`. -### @ Bindings + + + + +### Using @ Bindings The *at* operator `@` lets us create a variable that holds a value at the same time we’re testing that value for a pattern match. In Listing 19-29, we want to test that a `Message::Hello` `id` field is within the range `3..=7`. We also -want to bind the value to the variable `id` so we can use it in the code +want to bind the value to the variable `id` so that we can use it in the code associated with the arm. @@ -1252,9 +1281,9 @@ associated with the arm. let msg = Message::Hello { id: 5 }; match msg { - Message::Hello { - id: id @ 3..=7, - } => println!("Found an id in range: {id}"), + Message::Hello { id: id @ 3..=7 } => { + println!("Found an id in range: {id}") + } Message::Hello { id: 10..=12 } => { println!("Found an id in another range") } @@ -1272,24 +1301,24 @@ In the second arm, where we only have a range specified in the pattern, the code associated with the arm doesn’t have a variable that contains the actual value of the `id` field. The `id` field’s value could have been 10, 11, or 12, but the code that goes with that pattern doesn’t know which it is. The pattern code -isn’t able to use the value from the `id` field, because we haven’t saved the +isn’t able to use the value from the `id` field because we haven’t saved the `id` value in a variable. In the last arm, where we’ve specified a variable without a range, we do have the value available to use in the arm’s code in a variable named `id`. The reason is that we’ve used the struct field shorthand syntax. But we haven’t applied any test to the value in the `id` field in this arm, as we did with the -first two arms: any value would match this pattern. +first two arms: Any value would match this pattern. Using `@` lets us test a value and save it in a variable within one pattern. ## Summary Rust’s patterns are very useful in distinguishing between different kinds of -data. When used in `match` expressions, Rust ensures your patterns cover every -possible value, or your program won’t compile. Patterns in `let` statements and -function parameters make those constructs more useful, enabling the -destructuring of values into smaller parts and assigning those parts to +data. When used in `match` expressions, Rust ensures that your patterns cover +every possible value, or your program won’t compile. Patterns in `let` +statements and function parameters make those constructs more useful, enabling +the destructuring of values into smaller parts and assigning those parts to variables. We can create simple or complex patterns to suit our needs. Next, for the penultimate chapter of the book, we’ll look at some advanced diff --git a/nostarch/chapter20.md b/nostarch/chapter20.md index 3e0778bf3b..77a69459de 100644 --- a/nostarch/chapter20.md +++ b/nostarch/chapter20.md @@ -10,7 +10,7 @@ directory, so all fixes need to be made in `/src/`. By now, you’ve learned the most commonly used parts of the Rust programming language. Before we do one more project, in Chapter 21, we’ll look at a few -aspects of the language you might run into every once in a while, but may not +aspects of the language you might run into every once in a while but may not use every day. You can use this chapter as a reference for when you encounter any unknowns. The features covered here are useful in very specific situations. Although you might not reach for them often, we want to make sure you have a @@ -18,14 +18,14 @@ grasp of all the features Rust has to offer. In this chapter, we’ll cover: -* Unsafe Rust: how to opt out of some of Rust’s guarantees and take +* Unsafe Rust: How to opt out of some of Rust’s guarantees and take responsibility for manually upholding those guarantees -* Advanced traits: associated types, default type parameters, fully qualified +* Advanced traits: Associated types, default type parameters, fully qualified syntax, supertraits, and the newtype pattern in relation to traits -* Advanced types: more about the newtype pattern, type aliases, the never type, +* Advanced types: More about the newtype pattern, type aliases, the never type, and dynamically sized types -* Advanced functions and closures: function pointers and returning closures -* Macros: ways to define code that defines more code at compile time +* Advanced functions and closures: Function pointers and returning closures +* Macros: Ways to define code that defines more code at compile time It’s a panoply of Rust features with something for everyone! Let’s dive in! @@ -33,8 +33,8 @@ It’s a panoply of Rust features with something for everyone! Let’s dive in! All the code we’ve discussed so far has had Rust’s memory safety guarantees enforced at compile time. However, Rust has a second language hidden inside it -that doesn’t enforce these memory safety guarantees: it’s called *unsafe Rust* -and works just like regular Rust, but gives us extra superpowers. +that doesn’t enforce these memory safety guarantees: It’s called *unsafe Rust* +and works just like regular Rust but gives us extra superpowers. Unsafe Rust exists because, by nature, static analysis is conservative. When the compiler tries to determine whether or not code upholds the guarantees, @@ -42,7 +42,7 @@ it’s better for it to reject some valid programs than to accept some invalid programs. Although the code *might* be okay, if the Rust compiler doesn’t have enough information to be confident, it will reject the code. In these cases, you can use unsafe code to tell the compiler, “Trust me, I know what I’m -doing.” Be warned, however, that you use unsafe Rust at your own risk: if you +doing.” Be warned, however, that you use unsafe Rust at your own risk: If you use unsafe code incorrectly, problems can occur due to memory unsafety, such as null pointer dereferencing. @@ -54,29 +54,33 @@ writing your own operating system. Working with low-level systems programming is one of the goals of the language. Let’s explore what we can do with unsafe Rust and how to do it. -### Unsafe Superpowers + + + + +### Performing Unsafe Superpowers To switch to unsafe Rust, use the `unsafe` keyword and then start a new block that holds the unsafe code. You can take five actions in unsafe Rust that you can’t in safe Rust, which we call *unsafe superpowers*. Those superpowers include the ability to: -1. Dereference a raw pointer -1. Call an unsafe function or method -1. Access or modify a mutable static variable -1. Implement an unsafe trait -1. Access fields of `union`s +1. Dereference a raw pointer. +1. Call an unsafe function or method. +1. Access or modify a mutable static variable. +1. Implement an unsafe trait. +1. Access fields of `union`s. It’s important to understand that `unsafe` doesn’t turn off the borrow checker -or disable any of Rust’s other safety checks: if you use a reference in unsafe +or disable any of Rust’s other safety checks: If you use a reference in unsafe code, it will still be checked. The `unsafe` keyword only gives you access to these five features that are then not checked by the compiler for memory safety. You’ll still get some degree of safety inside an unsafe block. In addition, `unsafe` does not mean the code inside the block is necessarily -dangerous or that it will definitely have memory safety problems: the intent is -that as the programmer, you’ll ensure the code inside an `unsafe` block will -access memory in a valid way. +dangerous or that it will definitely have memory safety problems: The intent is +that as the programmer, you’ll ensure that the code inside an `unsafe` block +will access memory in a valid way. People are fallible and mistakes will happen, but by requiring these five unsafe operations to be inside blocks annotated with `unsafe`, you’ll know that @@ -98,12 +102,13 @@ some abstractions that provide a safe interface to unsafe code. ### Dereferencing a Raw Pointer -In Chapter 4, in “Dangling References”, we -mentioned that the compiler ensures references are always valid. Unsafe Rust has -two new types called *raw pointers* that are similar to references. As with -references, raw pointers can be immutable or mutable and are written as `*const T` and `*mut T`, respectively. The asterisk isn’t the dereference operator; it’s -part of the type name. In the context of raw pointers, *immutable* means that -the pointer can’t be directly assigned to after being dereferenced. +In Chapter 4, in the “Dangling References” section, we mentioned that the compiler ensures that references are always +valid. Unsafe Rust has two new types called *raw pointers* that are similar to +references. As with references, raw pointers can be immutable or mutable and +are written as `*const T` and `*mut T`, respectively. The asterisk isn’t the +dereference operator; it’s part of the type name. In the context of raw +pointers, *immutable* means that the pointer can’t be directly assigned to +after being dereferenced. Different from references and smart pointers, raw pointers: @@ -141,9 +146,9 @@ that assumption about just any raw pointer. To demonstrate this, next we’ll create a raw pointer whose validity we can’t be so certain of, using the keyword `as` to cast a value instead of using the raw borrow operator. Listing 20-2 shows how to create a raw pointer to an arbitrary -location in memory. Trying to use arbitrary memory is undefined: there might be +location in memory. Trying to use arbitrary memory is undefined: There might be data at that address or there might not, the compiler might optimize the code -so there is no memory access, or the program might terminate with a +so that there is no memory access, or the program might terminate with a segmentation fault. Usually, there is no good reason to write code like this, especially in cases where you can use a raw borrow operator instead, but it is possible. @@ -156,7 +161,7 @@ possible. Listing 20-2: Creating a raw pointer to an arbitrary memory address -Recall that we can create raw pointers in safe code, but we can’t *dereference* +Recall that we can create raw pointers in safe code, but we can’t dereference raw pointers and read the data being pointed to. In Listing 20-3, we use the dereference operator `*` on a raw pointer that requires an `unsafe` block. @@ -248,7 +253,7 @@ Just because a function contains unsafe code doesn’t mean we need to mark the entire function as unsafe. In fact, wrapping unsafe code in a safe function is a common abstraction. As an example, let’s study the `split_at_mut` function from the standard library, which requires some unsafe code. We’ll explore how -we might implement it. This safe method is defined on mutable slices: it takes +we might implement it. This safe method is defined on mutable slices: It takes one slice and makes it two by splitting the slice at the index given as an argument. Listing 20-4 shows how to use `split_at_mut`. @@ -284,13 +289,13 @@ fn split_at_mut(values: &mut [i32], mid: usize) -> (&mut [i32], &mut [i32]) { Listing 20-5: An attempted implementation of `split_at_mut` using only safe Rust -This function first gets the total length of the slice. Then it asserts that +This function first gets the total length of the slice. Then, it asserts that the index given as a parameter is within the slice by checking whether it’s less than or equal to the length. The assertion means that if we pass an index that is greater than the length to split the slice at, the function will panic before it attempts to use that index. -Then we return two mutable slices in a tuple: one from the start of the +Then, we return two mutable slices in a tuple: one from the start of the original slice to the `mid` index and another from `mid` to the end of the slice. @@ -348,26 +353,26 @@ fn split_at_mut(values: &mut [i32], mid: usize) -> (&mut [i32], &mut [i32]) { Listing 20-6: Using unsafe code in the implementation of the `split_at_mut` function -Recall from “The Slice Type” in Chapter 4 that -a slice is a pointer to some data and the length of the slice. We use the `len` -method to get the length of a slice and the `as_mut_ptr` method to access the -raw pointer of a slice. In this case, because we have a mutable slice to `i32` -values, `as_mut_ptr` returns a raw pointer with the type `*mut i32`, which we’ve -stored in the variable `ptr`. +Recall from “The Slice Type” section in +Chapter 4 that a slice is a pointer to some data and the length of the slice. +We use the `len` method to get the length of a slice and the `as_mut_ptr` +method to access the raw pointer of a slice. In this case, because we have a +mutable slice to `i32` values, `as_mut_ptr` returns a raw pointer with the type +`*mut i32`, which we’ve stored in the variable `ptr`. -We keep the assertion that the `mid` index is within the slice. Then we get to -the unsafe code: the `slice::from_raw_parts_mut` function takes a raw pointer +We keep the assertion that the `mid` index is within the slice. Then, we get to +the unsafe code: The `slice::from_raw_parts_mut` function takes a raw pointer and a length, and it creates a slice. We use this function to create a slice -that starts from `ptr` and is `mid` items long. Then we call the `add` -method on `ptr` with `mid` as an argument to get a raw pointer that starts at -`mid`, and we create a slice using that pointer and the remaining number of -items after `mid` as the length. +that starts from `ptr` and is `mid` items long. Then, we call the `add` method +on `ptr` with `mid` as an argument to get a raw pointer that starts at `mid`, +and we create a slice using that pointer and the remaining number of items +after `mid` as the length. The function `slice::from_raw_parts_mut` is unsafe because it takes a raw pointer and must trust that this pointer is valid. The `add` method on raw pointers is also unsafe because it must trust that the offset location is also a valid pointer. Therefore, we had to put an `unsafe` block around our calls to -`slice::from_raw_parts_mut` and `add` so we could call them. By looking at +`slice::from_raw_parts_mut` and `add` so that we could call them. By looking at the code and by adding the assertion that `mid` must be less than or equal to `len`, we can tell that all the raw pointers used within the `unsafe` block will be valid pointers to data within the slice. This is an acceptable and @@ -433,13 +438,13 @@ Listing 20-8: Declaring and calling an `extern` function defined in another lang Within the `unsafe extern "C"` block, we list the names and signatures of external functions from another language we want to call. The `"C"` part defines which *application binary interface (ABI)* the external function uses: -the ABI defines how to call the function at the assembly level. The `"C"` ABI +The ABI defines how to call the function at the assembly level. The `"C"` ABI is the most common and follows the C programming language’s ABI. Information about all the ABIs Rust supports is available in the Rust Reference at *../reference/items/external-blocks.html#abi*. Every item declared within an `unsafe extern` block is implicitly unsafe. However, some FFI functions *are* safe to call. For example, the `abs` function -from C’s standard library does not have any memory safety considerations and we +from C’s standard library does not have any memory safety considerations, and we know it can be called with any `i32`. In cases like this, we can use the `safe` keyword to say that this specific function is safe to call even though it is in an `unsafe extern` block. Once we make that change, calling it no longer @@ -495,8 +500,9 @@ This usage of `extern` requires `unsafe` only in the attribute, not on the ### Accessing or Modifying a Mutable Static Variable In this book, we’ve not yet talked about global variables, which Rust does -support but can be problematic with Rust’s ownership rules. If two threads are -accessing the same mutable global variable, it can cause a data race. +support but which can be problematic with Rust’s ownership rules. If two +threads are accessing the same mutable global variable, it can cause a data +race. In Rust, global variables are called *static* variables. Listing 20-10 shows an example declaration and use of a static variable with a string slice as a @@ -514,13 +520,12 @@ fn main() { Listing 20-10: Defining and using an immutable static variable -Static variables are similar to constants, which we discussed in -“Constants” in -Chapter 3. The names of static variables are in `SCREAMING_SNAKE_CASE` by -convention. Static variables can only store references with the `'static` -lifetime, which means the Rust compiler can figure out the lifetime and we -aren’t required to annotate it explicitly. Accessing an immutable static -variable is safe. +Static variables are similar to constants, which we discussed in the +“Declaring Constants” section in Chapter 3. The +names of static variables are in `SCREAMING_SNAKE_CASE` by convention. Static +variables can only store references with the `'static` lifetime, which means +the Rust compiler can figure out the lifetime and we aren’t required to +annotate it explicitly. Accessing an immutable static variable is safe. A subtle difference between constants and immutable static variables is that values in a static variable have a fixed address in memory. Using the value @@ -560,7 +565,7 @@ code that reads or writes from `COUNTER` must be within an `unsafe` block. The code in Listing 20-11 compiles and prints `COUNTER: 3` as we would expect because it’s single threaded. Having multiple threads access `COUNTER` would likely result in data races, so it is undefined behavior. Therefore, we need to -mark the entire function as `unsafe` and document the safety limitation, so +mark the entire function as `unsafe` and document the safety limitation so that anyone calling the function knows what they are and are not allowed to do safely. @@ -572,7 +577,7 @@ rules are upheld. Additionally, the compiler will deny by default any attempt to create references to a mutable static variable through a compiler lint. You must -either explicitly opt-out of that lint’s protections by adding an +either explicitly opt out of that lint’s protections by adding an `#[allow(static_mut_refs)]` annotation or access the mutable static variable via a raw pointer created with one of the raw borrow operators. That includes cases where the reference is created invisibly, as when it is used in the @@ -580,11 +585,11 @@ cases where the reference is created invisibly, as when it is used in the variables to be created via raw pointers helps make the safety requirements for using them more obvious. -With mutable data that is globally accessible, it’s difficult to ensure there -are no data races, which is why Rust considers mutable static variables to be -unsafe. Where possible, it’s preferable to use the concurrency techniques and -thread-safe smart pointers we discussed in Chapter 16 so the compiler checks -that data access from different threads is done safely. +With mutable data that is globally accessible, it’s difficult to ensure that +there are no data races, which is why Rust considers mutable static variables +to be unsafe. Where possible, it’s preferable to use the concurrency techniques +and thread-safe smart pointers we discussed in Chapter 16 so that the compiler +checks that data access from different threads is done safely. ### Implementing an Unsafe Trait @@ -610,17 +615,16 @@ Listing 20-12: Defining and implementing an unsafe trait By using `unsafe impl`, we’re promising that we’ll uphold the invariants that the compiler can’t verify. -As an example, recall the `Send` and `Sync` marker traits we discussed in -“Extensible Concurrency with the `Send` and `Sync` -Traits” -in Chapter 16: the compiler implements these traits automatically if our types -are composed entirely of other types that implement `Send` and `Sync`. If we -implement a type that contains a type that does not implement `Send` or `Sync`, -such as raw pointers, and we want to mark that type as `Send` or `Sync`, we -must use `unsafe`. Rust can’t verify that our type upholds the guarantees that -it can be safely sent across threads or accessed from multiple threads; -therefore, we need to do those checks manually and indicate as such with -`unsafe`. +As an example, recall the `Send` and `Sync` marker traits we discussed in the +“Extensible Concurrency with `Send` and `Sync`” +section in Chapter 16: The compiler implements these traits automatically if +our types are composed entirely of other types that implement `Send` and +`Sync`. If we implement a type that contains a type that does not implement +`Send` or `Sync`, such as raw pointers, and we want to mark that type as `Send` +or `Sync`, we must use `unsafe`. Rust can’t verify that our type upholds the +guarantees that it can be safely sent across threads or accessed from multiple +threads; therefore, we need to do those checks manually and indicate as such +with `unsafe`. ### Accessing Fields of a Union @@ -636,8 +640,8 @@ learn more about unions in the Rust Reference at *../reference/items/unions.html When writing unsafe code, you might want to check that what you have written actually is safe and correct. One of the best ways to do that is to use Miri, an official Rust tool for detecting undefined behavior. Whereas the borrow -checker is a *static* tool which works at compile time, Miri is a *dynamic* -tool which works at runtime. It checks your code by running your program, or +checker is a *static* tool that works at compile time, Miri is a *dynamic* +tool that works at runtime. It checks your code by running your program, or its test suite, and detecting when you violate the rules it understands about how Rust should work. @@ -655,11 +659,11 @@ against Listing 20-7. $ cargo +nightly miri run Compiling unsafe-example v0.1.0 (file:///projects/unsafe-example) Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.01s - Running `file:///home/.rustup/toolchains/nightly-aarch64-apple-darwin/bin/cargo-miri runner target/miri/aarch64-apple-darwin/debug/unsafe-example` + Running `file:///home/.rustup/toolchains/nightly/bin/cargo-miri runner target/miri/debug/unsafe-example` warning: integer-to-pointer cast - --> src/main.rs:6:13 + --> src/main.rs:5:13 | -6 | let r = address as *mut i32; +5 | let r = address as *mut i32; | ^^^^^^^^^^^^^^^^^^^ integer-to-pointer cast | = help: this program is using integer-to-pointer casts or (equivalently) `ptr::with_exposed_provenance`, which means that Miri might miss pointer bugs in this program @@ -668,18 +672,18 @@ warning: integer-to-pointer cast = help: you can then set `MIRIFLAGS=-Zmiri-strict-provenance` to ensure you are not relying on `with_exposed_provenance` semantics = help: alternatively, `MIRIFLAGS=-Zmiri-permissive-provenance` disables this warning = note: BACKTRACE: - = note: inside `main` at src/main.rs:6:13: 6:32 + = note: inside `main` at src/main.rs:5:13: 5:32 error: Undefined Behavior: pointer not dereferenceable: pointer must be dereferenceable for 40000 bytes, but got 0x1234[noalloc] which is a dangling pointer (it has no provenance) - --> src/main.rs:8:35 + --> src/main.rs:7:35 | -8 | let values: &[i32] = unsafe { slice::from_raw_parts_mut(r, 10000) }; +7 | let values: &[i32] = unsafe { slice::from_raw_parts_mut(r, 10000) }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Undefined Behavior occurred here | = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information = note: BACKTRACE: - = note: inside `main` at src/main.rs:8:35: 8:70 + = note: inside `main` at src/main.rs:7:35: 7:70 note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace @@ -688,12 +692,12 @@ error: aborting due to 1 previous error; 1 warning emitted ``` Miri correctly warns us that we’re casting an integer to a pointer, which might -be a problem but Miri can’t detect if there is because it doesn’t know how the -pointer originated. Then, Miri returns an error where Listing 20-7 has -undefined behavior because we have a dangling pointer. Thanks to Miri, we now -know there is a risk of undefined behavior, and we can think about how to make -the code safe. In some cases, Miri can even make recommendations about how to -fix errors. +be a problem, but Miri can’t determine whether a problem exists because it +doesn’t know how the pointer originated. Then, Miri returns an error where +Listing 20-7 has undefined behavior because we have a dangling pointer. Thanks +to Miri, we now know there is a risk of undefined behavior, and we can think +about how to make the code safe. In some cases, Miri can even make +recommendations about how to fix errors. Miri doesn’t catch everything you might get wrong when writing unsafe code. Miri is a dynamic analysis tool, so it only catches problems with code that @@ -708,7 +712,11 @@ this chapter and see what it says! You can learn more about Miri at its GitHub repository at *https://github.com/rust-lang/miri*. -### When to Use Unsafe Code + + + + +### Using Unsafe Code Correctly Using `unsafe` to use one of the five superpowers just discussed isn’t wrong or even frowned upon, but it is trickier to get `unsafe` code correct because the @@ -719,20 +727,21 @@ write unsafe code, you can use Miri to help you be more confident that the code you have written upholds Rust’s rules. For a much deeper exploration of how to work effectively with unsafe Rust, read -Rust’s official guide to the subject, the Rustonomicon at *https://doc.rust-lang.org/nomicon/*. +Rust’s official guide for `unsafe`, The Rustonomicon at *https://doc.rust-lang.org/nomicon/*. ## Advanced Traits -We first covered traits in “Traits: Defining Shared -Behavior” in Chapter 10, but we -didn’t discuss the more advanced details. Now that you know more about Rust, we -can get into the nitty-gritty. +We first covered traits in the “Defining Shared Behavior with +Traits” section in Chapter 10, but we didn’t discuss +the more advanced details. Now that you know more about Rust, we can get into +the nitty-gritty. - + + -### Associated Types +### Defining Traits with Associated Types *Associated types* connect a type placeholder with a trait such that the trait method definitions can use these placeholder types in their signatures. The @@ -742,7 +751,7 @@ trait that uses some types without needing to know exactly what those types are until the trait is implemented. We’ve described most of the advanced features in this chapter as being rarely -needed. Associated types are somewhere in the middle: they’re used more rarely +needed. Associated types are somewhere in the middle: They’re used more rarely than features explained in the rest of the book but more commonly than many of the other features discussed in this chapter. @@ -786,7 +795,7 @@ impl Iterator for Counter { -This syntax seems comparable to that of generics. So why not just define the +This syntax seems comparable to that of generics. So, why not just define the `Iterator` trait with generics, as shown in Listing 20-14? @@ -807,19 +816,23 @@ the concrete types of the generic type parameters each time. When we use the `next` method on `Counter`, we would have to provide type annotations to indicate which implementation of `Iterator` we want to use. -With associated types, we don’t need to annotate types because we can’t +With associated types, we don’t need to annotate types, because we can’t implement a trait on a type multiple times. In Listing 20-13 with the definition that uses associated types, we can choose what the type of `Item` will be only once because there can be only one `impl Iterator for Counter`. We don’t have to specify that we want an iterator of `u32` values everywhere we call `next` on `Counter`. -Associated types also become part of the trait’s contract: implementors of the +Associated types also become part of the trait’s contract: Implementors of the trait must provide a type to stand in for the associated type placeholder. Associated types often have a name that describes how the type will be used, and documenting the associated type in the API documentation is a good practice. -### Default Generic Type Parameters and Operator Overloading + + + + +### Using Default Generic Parameters and Operator Overloading When we use generic type parameters, we can specify a default concrete type for the generic type. This eliminates the need for implementors of the trait to @@ -833,7 +846,7 @@ in particular situations. Rust doesn’t allow you to create your own operators or overload arbitrary operators. But you can overload the operations and corresponding traits listed in `std::ops` by implementing the traits associated with the operator. For -example, in Listing 20-15 we overload the `+` operator to add two `Point` +example, in Listing 20-15, we overload the `+` operator to add two `Point` instances together. We do this by implementing the `Add` trait on a `Point` struct. @@ -886,7 +899,7 @@ trait Add { ``` This code should look generally familiar: a trait with one method and an -associated type. The new part is `Rhs=Self`: this syntax is called *default +associated type. The new part is `Rhs=Self`: This syntax is called *default type parameters*. The `Rhs` generic type parameter (short for “right-hand side”) defines the type of the `rhs` parameter in the `add` method. If we don’t specify a concrete type for `Rhs` when we implement the `Add` trait, the type @@ -900,10 +913,11 @@ default. We have two structs, `Millimeters` and `Meters`, holding values in different units. This thin wrapping of an existing type in another struct is known as the -*newtype pattern*, which we describe in more detail in the “Using the Newtype -Pattern to Implement External Traits” section. We want to add values in millimeters to values in meters and have -the implementation of `Add` do the conversion correctly. We can implement `Add` -for `Millimeters` with `Meters` as the `Rhs`, as shown in Listing 20-16. +*newtype pattern*, which we describe in more detail in the “Implementing +External Traits with the Newtype Pattern” section. We +want to add values in millimeters to values in meters and have the +implementation of `Add` do the conversion correctly. We can implement `Add` for +`Millimeters` with `Meters` as the `Rhs`, as shown in Listing 20-16. src/lib.rs @@ -933,22 +947,23 @@ You’ll use default type parameters in two main ways: 1. To allow customization in specific cases most users won’t need The standard library’s `Add` trait is an example of the second purpose: -usually, you’ll add two like types, but the `Add` trait provides the ability to +Usually, you’ll add two like types, but the `Add` trait provides the ability to customize beyond that. Using a default type parameter in the `Add` trait definition means you don’t have to specify the extra parameter most of the time. In other words, a bit of implementation boilerplate isn’t needed, making it easier to use the trait. -The first purpose is similar to the second but in reverse: if you want to add a +The first purpose is similar to the second but in reverse: If you want to add a type parameter to an existing trait, you can give it a default to allow extension of the functionality of the trait without breaking the existing implementation code. - + + -### Disambiguating Between Methods with the Same Name +### Disambiguating Between Identically Named Methods Nothing in Rust prevents a trait from having a method with the same name as another trait’s method, nor does Rust prevent you from implementing both traits @@ -1054,7 +1069,7 @@ trait to use based on the type of `self`. However, associated functions that are not methods don’t have a `self` parameter. When there are multiple types or traits that define non-method functions with the same function name, Rust doesn’t always know which type you -mean unless you use fully qualified syntax. For example, in Listing 20-20 we +mean unless you use fully qualified syntax. For example, in Listing 20-20, we create a trait for an animal shelter that wants to name all baby dogs Spot. We make an `Animal` trait with an associated non-method function `baby_name`. The `Animal` trait is implemented for the struct `Dog`, on which we also provide an @@ -1106,10 +1121,10 @@ A baby dog is called a Spot ``` This output isn’t what we wanted. We want to call the `baby_name` function that -is part of the `Animal` trait that we implemented on `Dog` so the code prints -`A baby dog is called a puppy`. The technique of specifying the trait name that -we used in Listing 20-19 doesn’t help here; if we change `main` to the code in -Listing 20-21, we’ll get a compilation error. +is part of the `Animal` trait that we implemented on `Dog` so that the code +prints `A baby dog is called a puppy`. The technique of specifying the trait +name that we used in Listing 20-19 doesn’t help here; if we change `main` to +the code in Listing 20-21, we’ll get a compilation error. src/main.rs @@ -1181,20 +1196,20 @@ In general, fully qualified syntax is defined as follows: ``` For associated functions that aren’t methods, there would not be a `receiver`: -there would only be the list of other arguments. You could use fully qualified +There would only be the list of other arguments. You could use fully qualified syntax everywhere that you call functions or methods. However, you’re allowed to omit any part of this syntax that Rust can figure out from other information in the program. You only need to use this more verbose syntax in cases where there are multiple implementations that use the same name and Rust needs help to identify which implementation you want to call. - + ### Using Supertraits -Sometimes you might write a trait definition that depends on another trait: for +Sometimes you might write a trait definition that depends on another trait: For a type to implement the first trait, you want to require that type to also implement the second trait. You would do this so that your trait definition can make use of the associated items of the second trait. The trait your trait @@ -1326,29 +1341,29 @@ Then, implementing the `OutlinePrint` trait on `Point` will compile successfully, and we can call `outline_print` on a `Point` instance to display it within an outline of asterisks. - + - -### Using the Newtype Pattern to Implement External Traits - -In “Implementing a Trait on a Type” in Chapter 10, we mentioned the orphan rule that states we’re only -allowed to implement a trait on a type if either the trait or the type, or -both, are local to our crate. It’s possible to get around this restriction -using the *newtype pattern*, which involves creating a new type in a tuple -struct. (We covered tuple structs in “Using Tuple Structs Without Named Fields -to Create Different Types” in Chapter 5.) The -tuple struct will have one field and be a thin wrapper around the type for -which we want to implement a trait. Then the wrapper type is local to our -crate, and we can implement the trait on the wrapper. *Newtype* is a term that -originates from the Haskell programming language. There is no runtime -performance penalty for using this pattern, and the wrapper type is elided at -compile time. + + +### Implementing External Traits with the Newtype Pattern + +In the “Implementing a Trait on a Type” section in Chapter 10, we mentioned the orphan rule that states +we’re only allowed to implement a trait on a type if either the trait or the +type, or both, are local to our crate. It’s possible to get around this +restriction using the newtype pattern, which involves creating a new type in a +tuple struct. (We covered tuple structs in the “Creating Different Types with +Tuple Structs” section in Chapter 5.) The tuple +struct will have one field and be a thin wrapper around the type for which we +want to implement a trait. Then, the wrapper type is local to our crate, and we +can implement the trait on the wrapper. *Newtype* is a term that originates +from the Haskell programming language. There is no runtime performance penalty +for using this pattern, and the wrapper type is elided at compile time. As an example, let’s say we want to implement `Display` on `Vec`, which the orphan rule prevents us from doing directly because the `Display` trait and the `Vec` type are defined outside our crate. We can make a `Wrapper` struct -that holds an instance of `Vec`; then we can implement `Display` on +that holds an instance of `Vec`; then, we can implement `Display` on `Wrapper` and use the `Vec` value, as shown in Listing 20-24. src/main.rs @@ -1374,7 +1389,7 @@ Listing 20-24: Creating a `Wrapper` type around `Vec` to implement `Disp The implementation of `Display` uses `self.0` to access the inner `Vec` because `Wrapper` is a tuple struct and `Vec` is the item at index 0 in the -tuple. Then we can use the functionality of the `Display` trait on `Wrapper`. +tuple. Then, we can use the functionality of the `Display` trait on `Wrapper`. The downside of using this technique is that `Wrapper` is a new type, so it doesn’t have the methods of the value it’s holding. We would have to implement @@ -1382,8 +1397,9 @@ all the methods of `Vec` directly on `Wrapper` such that the methods delegate to `self.0`, which would allow us to treat `Wrapper` exactly like a `Vec`. If we wanted the new type to have every method the inner type has, implementing the `Deref` trait on the `Wrapper` to return the inner type would -be a solution (we discussed implementing the `Deref` trait in “Treating Smart -Pointers Like Regular References with `Deref`” in Chapter 15). If we didn’t want the `Wrapper` type to have all the +be a solution (we discussed implementing the `Deref` trait in the “Treating +Smart Pointers Like Regular References” +section in Chapter 15). If we didn’t want the `Wrapper` type to have all the methods of the inner type—for example, to restrict the `Wrapper` type’s behavior—we would have to implement just the methods we do want manually. @@ -1394,24 +1410,28 @@ switch focus and look at some advanced ways to interact with Rust’s type syste The Rust type system has some features that we’ve so far mentioned but haven’t yet discussed. We’ll start by discussing newtypes in general as we examine why -newtypes are useful as types. Then we’ll move on to type aliases, a feature +they are useful as types. Then, we’ll move on to type aliases, a feature similar to newtypes but with slightly different semantics. We’ll also discuss the `!` type and dynamically sized types. -### Using the Newtype Pattern for Type Safety and Abstraction + + + -This section assumes you’ve read the earlier section “Using the Newtype Pattern -to Implement External Traits”. The newtype pattern is also useful for tasks beyond those we’ve -discussed so far, including statically enforcing that values are never confused -and indicating the units of a value. You saw an example of using newtypes to -indicate units in Listing 20-16: recall that the `Millimeters` and `Meters` -structs wrapped `u32` values in a newtype. If we wrote a function with a -parameter of type `Millimeters`, we wouldn’t be able to compile a program that -accidentally tried to call that function with a value of type `Meters` or a -plain `u32`. +### Type Safety and Abstraction with the Newtype Pattern + +This section assumes you’ve read the earlier section “Implementing External +Traits with the Newtype Pattern”. The newtype pattern +is also useful for tasks beyond those we’ve discussed so far, including +statically enforcing that values are never confused and indicating the units of +a value. You saw an example of using newtypes to indicate units in Listing +20-16: Recall that the `Millimeters` and `Meters` structs wrapped `u32` values +in a newtype. If we wrote a function with a parameter of type `Millimeters`, we +wouldn’t be able to compile a program that accidentally tried to call that +function with a value of type `Meters` or a plain `u32`. We can also use the newtype pattern to abstract away some implementation -details of a type: the new type can expose a public API that is different from +details of a type: The new type can expose a public API that is different from the API of the private inner type. Newtypes can also hide internal implementation. For example, we could provide a @@ -1419,11 +1439,17 @@ Newtypes can also hide internal implementation. For example, we could provide a associated with their name. Code using `People` would only interact with the public API we provide, such as a method to add a name string to the `People` collection; that code wouldn’t need to know that we assign an `i32` ID to names -internally. The newtype pattern is a lightweight way to achieve encapsulation to -hide implementation details, which we discussed in “Encapsulation that Hides -Implementation Details” in Chapter 18. +internally. The newtype pattern is a lightweight way to achieve encapsulation +to hide implementation details, which we discussed in the “Encapsulation that +Hides Implementation +Details” +section in Chapter 18. + + -### Creating Type Synonyms with Type Aliases + + +### Type Synonyms and Type Aliases Rust provides the ability to declare a *type alias* to give an existing type another name. For this we use the `type` keyword. For example, we can create @@ -1448,7 +1474,7 @@ values of type `i32`: ``` Because `Kilometers` and `i32` are the same type, we can add values of both -types and we can pass `Kilometers` values to functions that take `i32` +types and can pass `Kilometers` values to functions that take `i32` parameters. However, using this method, we don’t get the type-checking benefits that we get from the newtype pattern discussed earlier. In other words, if we mix up `Kilometers` and `i32` values somewhere, the compiler will not give us @@ -1462,7 +1488,7 @@ Box ``` Writing this lengthy type in function signatures and as type annotations all -over the code can be tiresome and error prone. Imagine having a project full of +over the code can be tiresome and error-prone. Imagine having a project full of code like that in Listing 20-25. @@ -1549,7 +1575,7 @@ pub trait Write { } ``` -The type alias helps in two ways: it makes code easier to write *and* it gives +The type alias helps in two ways: It makes code easier to write *and* it gives us a consistent interface across all of `std::io`. Because it’s an alias, it’s just another `Result`, which means we can use any methods that work on `Result` with it, as well as special syntax like the `?` operator. @@ -1586,9 +1612,9 @@ here in Listing 20-27. Listing 20-27: A `match` with an arm that ends in `continue` At the time, we skipped over some details in this code. In “The `match` -Control Flow Construct” in -Chapter 6, we discussed that `match` arms must all return the same type. So, -for example, the following code doesn’t work: +Control Flow Construct” +section in Chapter 6, we discussed that `match` arms must all return the same +type. So, for example, the following code doesn’t work: ``` let guess = match guess.trim().parse() { @@ -1598,7 +1624,7 @@ for example, the following code doesn’t work: ``` The type of `guess` in this code would have to be an integer *and* a string, -and Rust requires that `guess` have only one type. So what does `continue` +and Rust requires that `guess` have only one type. So, what does `continue` return? How were we allowed to return a `u32` from one arm and have another arm that ends with `continue` in Listing 20-27? @@ -1634,7 +1660,7 @@ of the overall `match` expression is `T`. This code works because `panic!` doesn’t produce a value; it ends the program. In the `None` case, we won’t be returning a value from `unwrap`, so this code is valid. -One final expression that has the type `!` is a `loop`: +One final expression that has the type `!` is a loop: ``` print!("forever "); @@ -1675,27 +1701,26 @@ same amount of space. But they have different lengths: `s1` needs 12 bytes of storage and `s2` needs 15. This is why it’s not possible to create a variable holding a dynamically sized type. -So what do we do? In this case, you already know the answer: we make the types -of `s1` and `s2` a `&str` rather than a `str`. Recall from “String -Slices” in Chapter 4 that the slice data -structure just stores the starting position and the length of the slice. So, -although a `&T` is a single value that stores the memory address of where the -`T` is located, a `&str` is *two* values: the address of the `str` and its -length. As such, we can know the size of a `&str` value at compile time: it’s -twice the length of a `usize`. That is, we always know the size of a `&str`, no -matter how long the string it refers to is. In general, this is the way in which -dynamically sized types are used in Rust: they have an extra bit of metadata -that stores the size of the dynamic information. The golden rule of dynamically -sized types is that we must always put values of dynamically sized types behind -a pointer of some kind. +So, what do we do? In this case, you already know the answer: We make the type +of `s1` and `s2` string slice (`&str`) rather than `str`. Recall from the +“String Slices” section in Chapter 4 that the +slice data structure only stores the starting position and the length of the +slice. So, although `&T` is a single value that stores the memory address of +where the `T` is located, a string slice is *two* values: the address of the +`str` and its length. As such, we can know the size of a string slice value at +compile time: It’s twice the length of a `usize`. That is, we always know the +size of a string slice, no matter how long the string it refers to is. In +general, this is the way in which dynamically sized types are used in Rust: +They have an extra bit of metadata that stores the size of the dynamic +information. The golden rule of dynamically sized types is that we must always +put values of dynamically sized types behind a pointer of some kind. We can combine `str` with all kinds of pointers: for example, `Box` or `Rc`. In fact, you’ve seen this before but with a different dynamically sized type: traits. Every trait is a dynamically sized type we can refer to by -using the name of the trait. In “Using Trait Objects to Abstract over Shared -Behavior” -in Chapter 18, we mentioned that to use traits as trait objects, we must put -them behind a pointer, such as `&dyn Trait` or `Box` (`Rc` would work too). +using the name of the trait. In the “Using Trait Objects to Abstract over +Shared Behavior” section in Chapter 18, we mentioned that to use traits as trait +objects, we must put them behind a pointer, such as `&dyn Trait` or `Box` (`Rc` would work too). To work with DSTs, Rust provides the `Sized` trait to determine whether or not a type’s size is known at compile time. This trait is automatically implemented @@ -1727,7 +1752,7 @@ fn generic(t: &T) { } ``` -A trait bound on `?Sized` means “`T` may or may not be `Sized`” and this +A trait bound on `?Sized` means “`T` may or may not be `Sized`,” and this notation overrides the default that generic types must have a known size at compile time. The `?Trait` syntax with this meaning is only available for `Sized`, not any other traits. @@ -1794,7 +1819,7 @@ of the `Fn` traits as a trait bound. Function pointers implement all three of the closure traits (`Fn`, `FnMut`, and `FnOnce`), meaning you can always pass a function pointer as an argument for a function that expects a closure. It’s best to write functions using a generic -type and one of the closure traits so your functions can accept either +type and one of the closure traits so that your functions can accept either functions or closures. That said, one example of where you would want to only accept `fn` and not @@ -1825,21 +1850,21 @@ Listing 20-30 shows what this would look like. list_of_numbers.iter().map(ToString::to_string).collect(); ``` -Listing 20-30: Using the `String::to_string` function with the `map` method method to convert numbers to strings +Listing 20-30: Using the `String::to_string` function with the `map` method to convert numbers to strings -Note that we must use the fully qualified syntax that we talked about in -“Advanced Traits” because there are multiple -functions available named `to_string`. +Note that we must use the fully qualified syntax that we talked about in the +“Advanced Traits” section because there are +multiple functions available named `to_string`. Here, we’re using the `to_string` function defined in the `ToString` trait, which the standard library has implemented for any type that implements `Display`. -Recall from “Enum Values” in Chapter 6 that the -name of each enum variant that we define also becomes an initializer function. -We can use these initializer functions as function pointers that implement the -closure traits, which means we can specify the initializer functions as -arguments for methods that take closures, as seen in Listing 20-31. +Recall from the “Enum Values” section in Chapter +6 that the name of each enum variant that we define also becomes an initializer +function. We can use these initializer functions as function pointers that +implement the closure traits, which means we can specify the initializer +functions as arguments for methods that take closures, as seen in Listing 20-31. ``` @@ -1869,7 +1894,7 @@ pointer `fn` as a return type if the closure captures any values from its scope, for example. Instead, you will normally use the `impl Trait` syntax we learned about in -Chapter 10. You can return any function type, using `Fn`, `FnOnce` and `FnMut`. +Chapter 10. You can return any function type, using `Fn`, `FnOnce`, and `FnMut`. For example, the code in Listing 20-32 will compile just fine. @@ -1881,12 +1906,12 @@ fn returns_closure() -> impl Fn(i32) -> i32 { Listing 20-32: Returning a closure from a function using the `impl Trait` syntax -However, as we noted in “Closure Type Inference and -Annotation” in Chapter 13, each closure is also -its own distinct type. If you need to work with multiple functions that have the -same signature but different implementations, you will need to use a trait -object for them. Consider what happens if you write code like that shown in -Listing 20-33. +However, as we noted in the “Inferring and Annotating Closure +Types” section in Chapter 13, each closure is +also its own distinct type. If you need to work with multiple functions that +have the same signature but different implementations, you will need to use a +trait object for them. Consider what happens if you write code like that shown +in Listing 20-33. src/main.rs @@ -1941,12 +1966,12 @@ error: could not compile `functions-example` (bin "functions-example") due to 1 The error message tells us that whenever we return an `impl Trait`, Rust creates a unique *opaque type*, a type where we cannot see into the details of what Rust constructs for us, nor can we guess the type Rust will generate to -write ourselves. So even though these functions return closures that implement +write ourselves. So, even though these functions return closures that implement the same trait, `Fn(i32) -> i32`, the opaque types Rust generates for each are distinct. (This is similar to how Rust produces different concrete types for distinct async blocks even when they have the same output type, as we saw in -“Working with Any Number of Futures” in -Chapter 17.) We have seen a solution to this problem a few times now: we can +“The `Pin` Type and the `Unpin` Trait” in +Chapter 17.) We have seen a solution to this problem a few times now: We can use a trait object, as in Listing 20-34. @@ -1960,11 +1985,11 @@ fn returns_initialized_closure(init: i32) -> Box i32> { } ``` -Listing 20-34: Creating a `Vec` of closures defined by functions that return `Box` so they have the same type +Listing 20-34: Creating a `Vec` of closures defined by functions that return `Box` so that they have the same type This code will compile just fine. For more about trait objects, refer to the -section “Using Trait Objects That Allow for Values of Different -Types” in Chapter 18. +section “Using Trait Objects To Abstract over Shared +Behavior” in Chapter 18. Next, let’s look at macros! @@ -1972,8 +1997,8 @@ Next, let’s look at macros! We’ve used macros like `println!` throughout this book, but we haven’t fully explored what a macro is and how it works. The term *macro* refers to a family -of features in Rust: *declarative* macros with `macro_rules!` and three kinds -of *procedural* macros: +of features in Rust—declarative macros with `macro_rules!` and three kinds of +procedural macros: * Custom `#[derive]` macros that specify code added with the `derive` attribute used on structs and enums @@ -1998,7 +2023,7 @@ some additional powers that functions don’t have. A function signature must declare the number and type of parameters the function has. Macros, on the other hand, can take a variable number of -parameters: we can call `println!("hello")` with one argument or +parameters: We can call `println!("hello")` with one argument or `println!("hello {}", name)` with two arguments. Also, macros are expanded before the compiler interprets the meaning of the code, so a macro can, for example, implement a trait on a given type. A function can’t, because it gets @@ -2014,7 +2039,11 @@ Another important difference between macros and functions is that you must define macros or bring them into scope *before* you call them in a file, as opposed to functions you can define anywhere and call anywhere. -### Declarative Macros with macro_rules! for General Metaprogramming + + + + +### Declarative Macros for General Metaprogramming The most widely used form of macros in Rust is the *declarative macro*. These are also sometimes referred to as “macros by example,” “`macro_rules!` macros,” @@ -2023,7 +2052,7 @@ something similar to a Rust `match` expression. As discussed in Chapter 6, `match` expressions are control structures that take an expression, compare the resultant value of the expression to patterns, and then run the code associated with the matching pattern. Macros also compare a value to patterns that are -associated with particular code: in this situation, the value is the literal +associated with particular code: In this situation, the value is the literal Rust source code passed to the macro; the patterns are compared with the structure of that source code; and the code associated with each pattern, when matched, replaces the code passed to the macro. This all happens during @@ -2090,7 +2119,7 @@ structure rather than values. Let’s walk through what the pattern pieces in Listing 20-29 mean; for the full macro pattern syntax, see the Rust Reference at *../reference/macros-by-example.html*. -First we use a set of parentheses to encompass the whole pattern. We use a +First, we use a set of parentheses to encompass the whole pattern. We use a dollar sign (`$`) to declare a variable in the macro system that will contain the Rust code matching the pattern. The dollar sign makes it clear this is a macro variable as opposed to a regular Rust variable. Next comes a set of @@ -2148,7 +2177,7 @@ macro variety. src/lib.rs ``` -use proc_macro; +use proc_macro::TokenStream; #[some_attribute] pub fn some_name(input: TokenStream) -> TokenStream { @@ -2160,7 +2189,7 @@ Listing 20-36: An example of defining a procedural macro The function that defines a procedural macro takes a `TokenStream` as an input and produces a `TokenStream` as an output. The `TokenStream` type is defined by the `proc_macro` crate that is included with Rust and represents a sequence of -tokens. This is the core of the macro: the source code that the macro is +tokens. This is the core of the macro: The source code that the macro is operating on makes up the input `TokenStream`, and the code the macro produces is the output `TokenStream`. The function also has an attribute attached to it that specifies which kind of procedural macro we’re creating. We can have @@ -2170,12 +2199,16 @@ Let’s look at the different kinds of procedural macros. We’ll start with a custom `derive` macro and then explain the small dissimilarities that make the other forms different. -### How to Write a Custom derive Macro + + + + +### Custom derive Macros Let’s create a crate named `hello_macro` that defines a trait named `HelloMacro` with one associated function named `hello_macro`. Rather than making our users implement the `HelloMacro` trait for each of their types, -we’ll provide a procedural macro so users can annotate their type with +we’ll provide a procedural macro so that users can annotate their type with `#[derive(HelloMacro)]` to get a default implementation of the `hello_macro` function. The default implementation will print `Hello, Macro! My name is TypeName!` where `TypeName` is the name of the type on which this trait has been defined. In other words, we’ll write a crate that enables another @@ -2252,7 +2285,7 @@ name at runtime. We need a macro to generate code at compile time. The next step is to define the procedural macro. At the time of this writing, procedural macros need to be in their own crate. Eventually, this restriction might be lifted. The convention for structuring crates and macro crates is as -follows: for a crate named `foo`, a custom `derive` procedural macro crate is +follows: For a crate named `foo`, a custom `derive` procedural macro crate is called `foo_derive`. Let’s start a new crate called `hello_macro_derive` inside our `hello_macro` project: @@ -2314,14 +2347,14 @@ Listing 20-40: Code that most procedural macro crates will require in order to p Notice that we’ve split the code into the `hello_macro_derive` function, which is responsible for parsing the `TokenStream`, and the `impl_hello_macro` -function, which is responsible for transforming the syntax tree: this makes +function, which is responsible for transforming the syntax tree: This makes writing a procedural macro more convenient. The code in the outer function (`hello_macro_derive` in this case) will be the same for almost every procedural macro crate you see or create. The code you specify in the body of the inner function (`impl_hello_macro` in this case) will be different depending on your procedural macro’s purpose. -We’ve introduced three new crates: `proc_macro`, `syn` at *https://crates.io/crates/syn*, +We’ve introduced three new crates: `proc_macro`, `syn`, and `quote`. The `proc_macro` crate comes with Rust, so we didn’t need to add that to the dependencies in *Cargo.toml*. The `proc_macro` crate is the compiler’s API that allows us to read and manipulate @@ -2330,7 +2363,7 @@ Rust code from our code. The `syn` crate parses Rust code from a string into a data structure that we can perform operations on. The `quote` crate turns `syn` data structures back into Rust code. These crates make it much simpler to parse any sort of Rust -code we might want to handle: writing a full parser for Rust code is no simple +code we might want to handle: Writing a full parser for Rust code is no simple task. The `hello_macro_derive` function will be called when a user of our library @@ -2414,18 +2447,18 @@ Listing 20-42: Implementing the `HelloMacro` trait using the parsed Rust code We get an `Ident` struct instance containing the name (identifier) of the annotated type using `ast.ident`. The struct in Listing 20-41 shows that when we run the `impl_hello_macro` function on the code in Listing 20-37, the -`ident` we get will have the `ident` field with a value of `"Pancakes"`. Thus +`ident` we get will have the `ident` field with a value of `"Pancakes"`. Thus, the `name` variable in Listing 20-42 will contain an `Ident` struct instance that, when printed, will be the string `"Pancakes"`, the name of the struct in Listing 20-37. The `quote!` macro lets us define the Rust code that we want to return. The -compiler expects something different to the direct result of the `quote!` +compiler expects something different from the direct result of the `quote!` macro’s execution, so we need to convert it to a `TokenStream`. We do this by calling the `into` method, which consumes this intermediate representation and returns a value of the required `TokenStream` type. -The `quote!` macro also provides some very cool templating mechanics: we can +The `quote!` macro also provides some very cool templating mechanics: We can enter `#name`, and `quote!` will replace it with the value in the variable `name`. You can even do some repetition similar to the way regular macros work. Check out the `quote` crate’s docs at *https://docs.rs/quote* for a thorough introduction. @@ -2439,10 +2472,11 @@ the name of the annotated type. The `stringify!` macro used here is built into Rust. It takes a Rust expression, such as `1 + 2`, and at compile time turns the expression into a string literal, such as `"1 + 2"`. This is different from `format!` or -`println!`, macros which evaluate the expression and then turn the result into -a `String`. There is a possibility that the `#name` input might be an -expression to print literally, so we use `stringify!`. Using `stringify!` also -saves an allocation by converting `#name` to a string literal at compile time. +`println!`, which are macros that evaluate the expression and then turn the +result into a `String`. There is a possibility that the `#name` input might be +an expression to print literally, so we use `stringify!`. Using `stringify!` +also saves an allocation by converting `#name` to a string literal at compile +time. At this point, `cargo build` should complete successfully in both `hello_macro` and `hello_macro_derive`. Let’s hook up these crates to the code in Listing @@ -2460,8 +2494,8 @@ hello_macro = { path = "../hello_macro" } hello_macro_derive = { path = "../hello_macro/hello_macro_derive" } ``` -Put the code in Listing 20-37 into *src/main.rs*, and run `cargo run`: it -should print `Hello, Macro! My name is Pancakes!` The implementation of the +Put the code in Listing 20-37 into *src/main.rs*, and run `cargo run`: It +should print `Hello, Macro! My name is Pancakes!`. The implementation of the `HelloMacro` trait from the procedural macro was included without the `pancakes` crate needing to implement it; the `#[derive(HelloMacro)]` added the trait implementation. @@ -2497,16 +2531,16 @@ item the attribute is attached to: in this case, `fn index() {}` and the rest of the function’s body. Other than that, attribute-like macros work the same way as custom `derive` -macros: you create a crate with the `proc-macro` crate type and implement a +macros: You create a crate with the `proc-macro` crate type and implement a function that generates the code you want! ### Function-Like Macros Function-like macros define macros that look like function calls. Similarly to `macro_rules!` macros, they’re more flexible than functions; for example, they -can take an unknown number of arguments. However, `macro_rules!` macros can only -be defined using the match-like syntax we discussed in “Declarative Macros with -`macro_rules!` for General Metaprogramming” earlier. +can take an unknown number of arguments. However, `macro_rules!` macros can +only be defined using the match-like syntax we discussed in the “Declarative +Macros for General Metaprogramming” section earlier. Function-like macros take a `TokenStream` parameter, and their definition manipulates that `TokenStream` using Rust code as the other two types of procedural macros do. An example of a function-like macro is an `sql!` macro @@ -2525,7 +2559,7 @@ syntactically correct, which is much more complex processing than a pub fn sql(input: TokenStream) -> TokenStream { ``` -This definition is similar to the custom `derive` macro’s signature: we receive +This definition is similar to the custom `derive` macro’s signature: We receive the tokens that are inside the parentheses and return the code we wanted to generate. diff --git a/nostarch/chapter21.md b/nostarch/chapter21.md index 80f2aea182..32bcd7db9d 100644 --- a/nostarch/chapter21.md +++ b/nostarch/chapter21.md @@ -13,7 +13,7 @@ chapter, we’ll build one more project together to demonstrate some of the concepts we covered in the final chapters, as well as recap some earlier lessons. -For our final project, we’ll make a web server that says “hello” and looks like +For our final project, we’ll make a web server that says “Hello!” and looks like Figure 21-1 in a web browser. Here is our plan for building the web server: @@ -24,18 +24,18 @@ Here is our plan for building the web server: 1. Create a proper HTTP response. 1. Improve the throughput of our server with a thread pool. -!hello from rust at *img/trpl21-01.png* +Screenshot of a web browser visiting the address 127.0.0.1:8080 displaying a webpage with the text content “Hello! Hi from Rust” Figure 21-1: Our final shared project Before we get started, we should mention two details. First, the method we’ll use won’t be the best way to build a web server with Rust. Community members have published a number of production-ready crates available at -crates.io at *https://crates.io/* that provide more complete web server and thread -pool implementations than we’ll build. However, our intention in this chapter is -to help you learn, not to take the easy route. Because Rust is a systems -programming language, we can choose the level of abstraction we want to work -with and can go to a lower level than is possible or practical in other +crates.io at *https://crates.io/* that provide more complete web server and +thread pool implementations than we’ll build. However, our intention in this +chapter is to help you learn, not to take the easy route. Because Rust is a +systems programming language, we can choose the level of abstraction we want to +work with and can go to a lower level than is possible or practical in other languages. Second, we will not be using async and await here. Building a thread pool is a @@ -44,9 +44,9 @@ However, we will note how async and await might be applicable to some of the same problems we will see in this chapter. Ultimately, as we noted back in Chapter 17, many async runtimes use thread pools for managing their work. -We’ll therefore write the basic HTTP server and thread pool manually so you can -learn the general ideas and techniques behind the crates you might use in the -future. +We’ll therefore write the basic HTTP server and thread pool manually so that +you can learn the general ideas and techniques behind the crates you might use +in the future. ## Building a Single-Threaded Web Server @@ -116,7 +116,7 @@ because, in networking, connecting to a port to listen to is known as “binding to a port.” The `bind` function returns a `Result`, which indicates that it’s -possible for binding to fail. For example, if we ran two instances of our +possible for binding to fail, for example, if we ran two instances of our program and so had two programs listening to the same port. Because we’re writing a basic server just for learning purposes, we won’t worry about handling these kinds of errors; instead, we use `unwrap` to stop the program if @@ -124,8 +124,8 @@ errors happen. The `incoming` method on `TcpListener` returns an iterator that gives us a sequence of streams (more specifically, streams of type `TcpStream`). A single -*stream* represents an open connection between the client and the server. A -*connection* is the name for the full request and response process in which a +*stream* represents an open connection between the client and the server. +*Connection* is the name for the full request and response process in which a client connects to the server, the server generates a response, and the server closes the connection. As such, we will read from the `TcpStream` to see what the client sent and then write our response to the stream to send data back to @@ -169,8 +169,8 @@ part of the `drop` implementation. Browsers sometimes deal with closed connections by retrying, because the problem might be temporary. Browsers also sometimes open multiple connections to the server without sending -any requests, so that if they *do* later send requests, those requests can -happen faster. When this happens, our server will see each connection, +any requests so that if they *do* later send requests, those requests can +happen more quickly. When this occurs, our server will see each connection, regardless of whether there are any requests over that connection. Many versions of Chrome-based browsers do this, for example; you can disable that optimization by using private browsing mode or using a different browser. @@ -179,7 +179,7 @@ The important factor is that we’ve successfully gotten a handle to a TCP connection! Remember to stop the program by pressing ctrl-C when -you’re done running a particular version of the code. Then restart the program +you’re done running a particular version of the code. Then, restart the program by invoking the `cargo run` command after you’ve made each set of code changes to make sure you’re running the newest code. @@ -189,8 +189,8 @@ Let’s implement the functionality to read the request from the browser! To separate the concerns of first getting a connection and then taking some action with the connection, we’ll start a new function for processing connections. In this new `handle_connection` function, we’ll read data from the TCP stream and -print it so we can see the data being sent from the browser. Change the code to -look like Listing 21-2. +print it so that we can see the data being sent from the browser. Change the +code to look like Listing 21-2. src/main.rs @@ -224,7 +224,7 @@ fn handle_connection(mut stream: TcpStream) { Listing 21-2: Reading from the `TcpStream` and printing the data -We bring `std::io::prelude` and `std::io::BufReader` into scope to get access +We bring `std::io::BufReader` and `std::io::prelude` into scope to get access to traits and types that let us read from and write to the stream. In the `for` loop in the `main` function, instead of printing a message that says we made a connection, we now call the new `handle_connection` function and pass the @@ -240,7 +240,7 @@ lines in a vector by adding the `Vec<_>` type annotation. `BufReader` implements the `std::io::BufRead` trait, which provides the `lines` method. The `lines` method returns an iterator of `Result` by splitting the stream of data whenever it sees a newline -byte. To get each `String`, we map and `unwrap` each `Result`. The `Result` +byte. To get each `String`, we `map` and `unwrap` each `Result`. The `Result` might be an error if the data isn’t valid UTF-8 or if there was a problem reading from the stream. Again, a production program should handle these errors more gracefully, but we’re choosing to stop the program in the error case for @@ -249,17 +249,24 @@ simplicity. The browser signals the end of an HTTP request by sending two newline characters in a row, so to get one request from the stream, we take lines until we get a line that is the empty string. Once we’ve collected the lines into the -vector, we’re printing them out using pretty debug formatting so we can take a -look at the instructions the web browser is sending to our server. +vector, we’re printing them out using pretty debug formatting so that we can +take a look at the instructions the web browser is sending to our server. Let’s try this code! Start the program and make a request in a web browser again. Note that we’ll still get an error page in the browser, but our program’s output in the terminal will now look similar to this: + + ``` $ cargo run Compiling hello v0.1.0 (file:///projects/hello) - Finished dev [unoptimized + debuginfo] target(s) in 0.42s + Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.42s Running `target/debug/hello` Request: [ "GET / HTTP/1.1", @@ -289,7 +296,12 @@ from our program. Let’s break down this request data to understand what the browser is asking of our program. -### A Closer Look at an HTTP Request + + + + + +### Looking More Closely at an HTTP Request HTTP is a text-based protocol, and a request takes this format: @@ -300,19 +312,19 @@ message-body ``` The first line is the *request line* that holds information about what the -client is requesting. The first part of the request line indicates the *method* +client is requesting. The first part of the request line indicates the method being used, such as `GET` or `POST`, which describes how the client is making this request. Our client used a `GET` request, which means it is asking for information. The next part of the request line is */*, which indicates the *uniform resource -identifier* *(URI)* the client is requesting: a URI is almost, but not quite, +identifier* *(URI)* the client is requesting: A URI is almost, but not quite, the same as a *uniform resource locator* *(URL)*. The difference between URIs and URLs isn’t important for our purposes in this chapter, but the HTTP spec uses the term *URI*, so we can just mentally substitute *URL* for *URI* here. The last part is the HTTP version the client uses, and then the request line -ends in a CRLF sequence. (CRLF stands for *carriage return* and *line feed*, +ends in a CRLF sequence. (*CRLF* stands for *carriage return* and *line feed*, which are terms from the typewriter days!) The CRLF sequence can also be written as `\r\n`, where `\r` is a carriage return and `\n` is a line feed. The *CRLF sequence* separates the request line from the rest of the request data. @@ -347,7 +359,7 @@ a reason phrase that provides a text description of the status code. After the CRLF sequence are any headers, another CRLF sequence, and the body of the response. -Here is an example response that uses HTTP version 1.1, and has a status code of +Here is an example response that uses HTTP version 1.1 and has a status code of 200, an OK reason phrase, no headers, and no body: ``` @@ -380,11 +392,11 @@ fn handle_connection(mut stream: TcpStream) { Listing 21-3: Writing a tiny successful HTTP response to the stream The first new line defines the `response` variable that holds the success -message’s data. Then we call `as_bytes` on our `response` to convert the string -data to bytes. The `write_all` method on `stream` takes a `&[u8]` and sends -those bytes directly down the connection. Because the `write_all` operation -could fail, we use `unwrap` on any error result as before. Again, in a real -application you would add error handling here. +message’s data. Then, we call `as_bytes` on our `response` to convert the +string data to bytes. The `write_all` method on `stream` takes a `&[u8]` and +sends those bytes directly down the connection. Because the `write_all` +operation could fail, we use `unwrap` on any error result as before. Again, in +a real application, you would add error handling here. With these changes, let’s run our code and make a request. We’re no longer printing any data to the terminal, so we won’t see any output other than the @@ -459,8 +471,8 @@ string should look familiar; we used it when we read the contents of a file for our I/O project in Listing 12-4. Next, we use `format!` to add the file’s contents as the body of the success -response. To ensure a valid HTTP response, we add the `Content-Length` header -which is set to the size of our response body, in this case the size of +response. To ensure a valid HTTP response, we add the `Content-Length` header, +which is set to the size of our response body—in this case, the size of `hello.html`. Run this code with `cargo run` and load *127.0.0.1:7878* in your browser; you @@ -478,7 +490,7 @@ request to */*. Right now, our web server will return the HTML in the file no matter what the client requested. Let’s add functionality to check that the browser is -requesting */* before returning the HTML file, and return an error if the +requesting */* before returning the HTML file and to return an error if the browser requests anything else. For this we need to modify `handle_connection`, as shown in Listing 21-6. This new code checks the content of the request received against what we know a request for */* looks like and adds `if` and @@ -558,7 +570,7 @@ Listing 21-7: Responding with status code 404 and an error page if anything othe Here, our response has a status line with status code 404 and the reason phrase `NOT FOUND`. The body of the response will be the HTML in the file *404.html*. You’ll need to create a *404.html* file next to *hello.html* for the error -page; again feel free to use any HTML you want, or use the example HTML in +page; again, feel free to use any HTML you want, or use the example HTML in Listing 21-8. 404.html @@ -583,16 +595,20 @@ With these changes, run your server again. Requesting *127.0.0.1:7878* should return the contents of *hello.html*, and any other request, like *127.0.0.1:7878/foo*, should return the error HTML from *404.html*. -### A Touch of Refactoring + + + + +### Refactoring -At the moment, the `if` and `else` blocks have a lot of repetition: they’re both -reading files and writing the contents of the files to the stream. The only -differences are the status line and the filename. Let’s make the code more +At the moment, the `if` and `else` blocks have a lot of repetition: They’re +both reading files and writing the contents of the files to the stream. The +only differences are the status line and the filename. Let’s make the code more concise by pulling out those differences into separate `if` and `else` lines -that will assign the values of the status line and the filename to variables; we -can then use those variables unconditionally in the code to read the file and -write the response. Listing 21-9 shows the resultant code after replacing the -large `if` and `else` blocks. +that will assign the values of the status line and the filename to variables; +we can then use those variables unconditionally in the code to read the file +and write the response. Listing 21-9 shows the resultant code after replacing +the large `if` and `else` blocks. src/main.rs @@ -638,16 +654,21 @@ requests with a 404 response. Currently, our server runs in a single thread, meaning it can only serve one request at a time. Let’s examine how that can be a problem by simulating some -slow requests. Then we’ll fix it so our server can handle multiple requests at -once. +slow requests. Then, we’ll fix it so that our server can handle multiple +requests at once. + + + + + -## Turning Our Single-Threaded Server into a Multithreaded Server +## From a Single-Threaded to a Multithreaded Server Right now, the server will process each request in turn, meaning it won’t -process a second connection until the first is finished processing. If the -server received more and more requests, this serial execution would be less and -less optimal. If the server receives a request that takes a long time to -process, subsequent requests will have to wait until the long request is +process a second connection until the first connection is finished processing. +If the server received more and more requests, this serial execution would be +less and less optimal. If the server receives a request that takes a long time +to process, subsequent requests will have to wait until the long request is finished, even if the new requests can be processed quickly. We’ll need to fix this, but first we’ll look at the problem in action. @@ -657,7 +678,7 @@ this, but first we’ll look at the problem in action. ### Simulating a Slow Request -We’ll look at how a slow-processing request can affect other requests made to +We’ll look at how a slowly processing request can affect other requests made to our current server implementation. Listing 21-10 implements handling a request to */sleep* with a simulated slow response that will cause the server to sleep for five seconds before responding. @@ -702,14 +723,14 @@ matches a request to */sleep*. When that request is received, the server will sleep for five seconds before rendering the successful HTML page. The third arm is the same as the `else` block from Listing 21-9. -You can see how primitive our server is: real libraries would handle the +You can see how primitive our server is: Real libraries would handle the recognition of multiple requests in a much less verbose way! -Start the server using `cargo run`. Then open two browser windows: one for -*http://127.0.0.1:7878* and the other for *http://127.0.0.1:7878/sleep*. If -you enter the */* URI a few times, as before, you’ll see it respond quickly. -But if you enter */sleep* and then load */*, you’ll see that */* waits until -`sleep` has slept for its full five seconds before loading. +Start the server using `cargo run`. Then, open two browser windows: one for +*http://127.0.0.1:7878* and the other for *http://127.0.0.1:7878/sleep*. If you +enter the */* URI a few times, as before, you’ll see it respond quickly. But if +you enter */sleep* and then load */*, you’ll see that */* waits until `sleep` +has slept for its full five seconds before loading. There are multiple techniques we could use to avoid requests backing up behind a slow request, including using async as we did Chapter 17; the one we’ll @@ -717,7 +738,7 @@ implement is a thread pool. ### Improving Throughput with a Thread Pool -A *thread pool* is a group of spawned threads that are waiting and ready to +A *thread pool* is a group of spawned threads that are ready and waiting to handle a task. When the program receives a new task, it assigns one of the threads in the pool to the task, and that thread will process the task. The remaining threads in the pool are available to handle any other tasks that come @@ -728,7 +749,7 @@ increasing the throughput of your server. We’ll limit the number of threads in the pool to a small number to protect us from DoS attacks; if we had our program create a new thread for each request as -it came in, someone making 10 million requests to our server could create havoc +it came in, someone making 10 million requests to our server could wreak havoc by using up all our server’s resources and grinding the processing of requests to a halt. @@ -751,10 +772,10 @@ options are possible. Before we begin implementing a thread pool, let’s talk about what using the pool should look like. When you’re trying to design code, writing the client -interface first can help guide your design. Write the API of the code so it’s -structured in the way you want to call it; then implement the functionality -within that structure rather than implementing the functionality and then -designing the public API. +interface first can help guide your design. Write the API of the code so that +it’s structured in the way you want to call it; then, implement the +functionality within that structure rather than implementing the functionality +and then designing the public API. Similar to how we used test-driven development in the project in Chapter 12, we’ll use compiler-driven development here. We’ll write the code that calls the @@ -771,7 +792,7 @@ we’ll explore the technique we’re not going to use as a starting point. First, let’s explore how our code might look if it did create a new thread for every connection. As mentioned earlier, this isn’t our final plan due to the problems with potentially spawning an unlimited number of threads, but it is a -starting point to get a working multithreaded server first. Then we’ll add the +starting point to get a working multithreaded server first. Then, we’ll add the thread pool as an improvement, and contrasting the two solutions will be easier. Listing 21-11 shows the changes to make to `main` to spawn a new thread to @@ -838,10 +859,10 @@ Listing 21-12: Our ideal `ThreadPool` interface We use `ThreadPool::new` to create a new thread pool with a configurable number of threads, in this case four. Then, in the `for` loop, `pool.execute` has a -similar interface as `thread::spawn` in that it takes a closure the pool should -run for each stream. We need to implement `pool.execute` so it takes the -closure and gives it to a thread in the pool to run. This code won’t yet -compile, but we’ll try so that the compiler can guide us in how to fix it. +similar interface as `thread::spawn` in that it takes a closure that the pool +should run for each stream. We need to implement `pool.execute` so that it +takes the closure and gives it to a thread in the pool to run. This code won’t +yet compile, but we’ll try so that the compiler can guide us in how to fix it. @@ -868,7 +889,7 @@ error: could not compile `hello` (bin "hello") due to 1 previous error Great! This error tells us we need a `ThreadPool` type or module, so we’ll build one now. Our `ThreadPool` implementation will be independent of the kind -of work our web server is doing. So let’s switch the `hello` crate from a +of work our web server is doing. So, let’s switch the `hello` crate from a binary crate to a library crate to hold our `ThreadPool` implementation. After we change to a library crate, we could also use the separate thread pool library for any work we want to do using a thread pool, not just for serving @@ -885,7 +906,7 @@ pub struct ThreadPool; -Then edit the *main.rs* file to bring `ThreadPool` into scope from the library +Then, edit the *main.rs* file to bring `ThreadPool` into scope from the library crate by adding the following code to the top of *src/main.rs*: src/main.rs @@ -935,7 +956,7 @@ impl ThreadPool { We chose `usize` as the type of the `size` parameter because we know that a negative number of threads doesn’t make any sense. We also know we’ll use this `4` as the number of elements in a collection of threads, which is what the -`usize` type is for, as discussed in “Integer Types” in Chapter 3. +`usize` type is for, as discussed in the “Integer Types” section in Chapter 3. Let’s check the code again: @@ -953,20 +974,20 @@ error: could not compile `hello` (bin "hello") due to 1 previous error ``` Now the error occurs because we don’t have an `execute` method on `ThreadPool`. -Recall from “Creating a Finite Number of -Threads” that we decided -our thread pool should have an interface similar to `thread::spawn`. In -addition, we’ll implement the `execute` function so it takes the closure it’s -given and gives it to an idle thread in the pool to run. +Recall from the “Creating a Finite Number of +Threads” section that we +decided our thread pool should have an interface similar to `thread::spawn`. In +addition, we’ll implement the `execute` function so that 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 “Moving Captured Values Out of the Closure and the `Fn` -Traits” 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 +Closures” 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: ``` pub fn spawn(f: F) -> JoinHandle @@ -985,7 +1006,7 @@ want to use because the thread for running a request will only execute that request’s closure one time, which matches the `Once` in `FnOnce`. The `F` type parameter also has the trait bound `Send` and the lifetime bound -`'static`, which are useful in our situation: we need `Send` to transfer the +`'static`, which are useful in our situation: We need `Send` to transfer the closure from one thread to another and `'static` because we don’t know how long the thread will take to execute. Let’s create an `execute` method on `ThreadPool` that will take a generic parameter of type `F` with these bounds: @@ -1010,7 +1031,7 @@ that takes no parameters and returns the unit type `()`. Just like function definitions, the return type can be omitted from the signature, but even if we have no parameters, we still need the parentheses. -Again, this is the simplest implementation of the `execute` method: it does +Again, this is the simplest implementation of the `execute` method: It does nothing, but we’re only trying to make our code compile. Let’s check it again: ``` @@ -1025,13 +1046,13 @@ the chapter. Our library isn’t actually calling the closure passed to `execute yet! > Note: A saying you might hear about languages with strict compilers, such as -> Haskell and Rust, is “if the code compiles, it works.” But this saying is not +> Haskell and Rust, is “If the code compiles, it works.” But this saying is not > universally true. Our project compiles, but it does absolutely nothing! If we > were building a real, complete project, this would be a good time to start > writing unit tests to check that the code compiles *and* has the behavior we > want. -Consider: what would be different here if we were going to execute a future +Consider: What would be different here if we were going to execute a future instead of a closure? #### Validating the Number of Threads in new @@ -1042,8 +1063,8 @@ let’s think about `new`. Earlier we chose an unsigned type for the `size` parameter because a pool with a negative number of threads makes no sense. However, a pool with zero threads also makes no sense, yet zero is a perfectly valid `usize`. We’ll add code to check that `size` is greater than zero before -we return a `ThreadPool` instance and have the program panic if it receives a -zero by using the `assert!` macro, as shown in Listing 21-13. +we return a `ThreadPool` instance, and we’ll have the program panic if it +receives a zero by using the `assert!` macro, as shown in Listing 21-13. src/lib.rs @@ -1105,7 +1126,7 @@ closure returns. Let’s try using `JoinHandle` too and see what happens. In our case, the closures we’re passing to the thread pool will handle the connection and not return anything, so `T` will be the unit type `()`. -The code in Listing 21-14 will compile but doesn’t create any threads yet. +The code in Listing 21-14 will compile, but it doesn’t create any threads yet. We’ve changed the definition of `ThreadPool` to hold a vector of `thread::JoinHandle<()>` instances, initialized the vector with a capacity of `size`, set up a `for` loop that will run some code to create the threads, and @@ -1145,7 +1166,7 @@ using `thread::JoinHandle` as the type of the items in the vector in Once a valid size is received, our `ThreadPool` creates a new vector that can hold `size` items. The `with_capacity` function performs the same task as -`Vec::new` but with an important difference: it pre-allocates space in the +`Vec::new` but with an important difference: It pre-allocates space in the vector. Because we know we need to store `size` elements in the vector, doing this allocation up front is slightly more efficient than using `Vec::new`, which resizes itself as elements are inserted. @@ -1173,16 +1194,17 @@ this data structure *Worker*, which is a common term in pooling implementations. The `Worker` picks up code that needs to be run and runs the code in its thread. -Think of people working in the kitchen at a restaurant: the workers wait until +Think of people working in the kitchen at a restaurant: The workers wait until orders come in from customers, and then they’re responsible for taking those orders and filling them. Instead of storing a vector of `JoinHandle<()>` instances in the thread pool, we’ll store instances of the `Worker` struct. Each `Worker` will store a single -`JoinHandle<()>` instance. Then we’ll implement a method on `Worker` that will +`JoinHandle<()>` instance. Then, we’ll implement a method on `Worker` that will take a closure of code to run and send it to the already running thread for -execution. We’ll also give each `Worker` an `id` so we can distinguish between -the different instances of `Worker` in the pool when logging or debugging. +execution. We’ll also give each `Worker` an `id` so that we can distinguish +between the different instances of `Worker` in the pool when logging or +debugging. Here is the new process that will happen when we create a `ThreadPool`. We’ll implement the code that sends the closure to the thread after we have `Worker` @@ -1371,7 +1393,7 @@ impl Worker { Listing 21-17: Passing the receiver to each `Worker` -We’ve made some small and straightforward changes: we pass the receiver into +We’ve made some small and straightforward changes: We pass the receiver into `Worker::new`, and then we use it inside the closure. When we try to check this code, we get this error: @@ -1407,7 +1429,7 @@ error: could not compile `hello` (lib) due to 1 previous error ``` The code is trying to pass `receiver` to multiple `Worker` instances. This -won’t work, as you’ll recall from Chapter 16: the channel implementation that +won’t work, as you’ll recall from Chapter 16: The channel implementation that Rust provides is multiple *producer*, single *consumer*. This means we can’t just clone the consuming end of the channel to fix this code. We also don’t want to send a message multiple times to multiple consumers; we want one list @@ -1418,7 +1440,7 @@ Additionally, taking a job off the channel queue involves mutating the `receiver`, so the threads need a safe way to share and modify `receiver`; otherwise, we might get race conditions (as covered in Chapter 16). -Recall the thread-safe smart pointers discussed in Chapter 16: to share +Recall the thread-safe smart pointers discussed in Chapter 16: To share ownership across multiple threads and allow the threads to mutate the value, we need to use `Arc>`. The `Arc` type will let multiple `Worker` instances own the receiver, and `Mutex` will ensure that only one `Worker` gets a job from @@ -1466,8 +1488,8 @@ impl Worker { Listing 21-18: Sharing the receiver among the `Worker` instances using `Arc` and `Mutex` In `ThreadPool::new`, we put the receiver in an `Arc` and a `Mutex`. For each -new `Worker`, we clone the `Arc` to bump the reference count so the `Worker` -instances can share ownership of the receiver. +new `Worker`, we clone the `Arc` to bump the reference count so that the +`Worker` instances can share ownership of the receiver. With these changes, the code compiles! We’re getting there! @@ -1475,10 +1497,9 @@ With these changes, the code compiles! We’re getting there! Let’s finally implement the `execute` method on `ThreadPool`. We’ll also change `Job` from a struct to a type alias for a trait object that holds the type of -closure that `execute` receives. As discussed in “Creating Type Synonyms with -Type Aliases” in -Chapter 20, type aliases allow us to make long types shorter for ease of use. -Look at Listing 21-19. +closure that `execute` receives. As discussed in the “Type Synonyms and Type +Aliases” section in Chapter 20, type aliases +allow us to make long types shorter for ease of use. Look at Listing 21-19. src/lib.rs @@ -1510,7 +1531,7 @@ send that job down the sending end of the channel. We’re calling `unwrap` on `send` for the case that sending fails. This might happen if, for example, we stop all our threads from executing, meaning the receiving end has stopped receiving new messages. At the moment, we can’t stop our threads from -executing: our threads continue executing as long as the pool exists. The +executing: Our threads continue executing as long as the pool exists. The reason we use `unwrap` is that we know the failure case won’t happen, but the compiler doesn’t know that. @@ -1652,8 +1673,8 @@ impl Worker { Listing 21-21: An alternative implementation of `Worker::new` using `while let` This code compiles and runs but doesn’t result in the desired threading -behavior: a slow request will still cause other requests to wait to be -processed. The reason is somewhat subtle: the `Mutex` struct has no public +behavior: A slow request will still cause other requests to wait to be +processed. The reason is somewhat subtle: The `Mutex` struct has no public `unlock` method because the ownership of the lock is based on the lifetime of the `MutexGuard` within the `LockResult>` that the `lock` method returns. At compile time, the borrow checker can then enforce the rule @@ -1663,7 +1684,7 @@ longer than intended if we aren’t mindful of the lifetime of the `MutexGuard`. The code in Listing 21-20 that uses `let job = receiver.lock().unwrap().recv().unwrap();` works because with `let`, any -temporary values used in the expression on the right hand side of the equal +temporary values used in the expression on the right-hand side of the equal sign are immediately dropped when the `let` statement ends. However, `while let` (and `if let` and `match`) does not drop temporary values until the end of the associated block. In Listing 21-21, the lock remains held for the duration of the call to `job()`, meaning other `Worker` instances cannot receive jobs. @@ -1679,15 +1700,15 @@ are stopped immediately as well, even if they’re in the middle of serving a request. Next, then, we’ll implement the `Drop` trait to call `join` on each of the -threads in the pool so they can finish the requests they’re working on before -closing. Then we’ll implement a way to tell the threads they should stop -accepting new requests and shut down. To see this code in action, we’ll modify -our server to accept only two requests before gracefully shutting down its -thread pool. +threads in the pool so that they can finish the requests they’re working on +before closing. Then, we’ll implement a way to tell the threads they should +stop accepting new requests and shut down. To see this code in action, we’ll +modify our server to accept only two requests before gracefully shutting down +its thread pool. -One thing to notice as we go: none of this affects the parts of the code that -handle executing the closures, so everything here would be just the same if we -were using a thread pool for an async runtime. +One thing to notice as we go: None of this affects the parts of the code that +handle executing the closures, so everything here would be the same if we were +using a thread pool for an async runtime. ### Implementing the Drop Trait on ThreadPool @@ -1712,7 +1733,7 @@ impl Drop for ThreadPool { Listing 21-22: Joining each thread when the thread pool goes out of scope -First we loop through each of the thread pool `workers`. We use `&mut` for this +First, we loop through each of the thread pool `workers`. We use `&mut` for this because `self` is a mutable reference, and we also need to be able to mutate `worker`. For each `worker`, we print a message saying that this particular `Worker` instance is shutting down, and then we call `join` on that `Worker` @@ -1739,30 +1760,31 @@ For more information about this error, try `rustc --explain E0507`. error: could not compile `hello` (lib) due to 1 previous error ``` -The error tells us we can’t call `join` because we only have a mutable borrow of -each `worker` and `join` takes ownership of its argument. To solve this issue, -we need to move the thread out of the `Worker` instance that owns `thread` so -`join` can consume the thread. One way to do this is by taking the same approach -we did in Listing 18-15. If `Worker` held an `Option>`, -we could call the `take` method on the `Option` to move the value out of the -`Some` variant and leave a `None` variant in its place. In other words, a -`Worker` that is running would have a `Some` variant in `thread`, and when we -wanted to clean up a `Worker`, we’d replace `Some` with `None` so the `Worker` -wouldn’t have a thread to run. - -However, the *only* time this would come up would be when dropping the `Worker`. -In exchange, we’d have to deal with an `Option>` anywhere -we accessed `worker.thread`. Idiomatic Rust uses `Option` quite a bit, but when -you find yourself wrapping something you know will always be present in an -`Option` as a workaround like this, it’s a good idea to look for alternative -approaches to make your code cleaner and less error-prone. +The error tells us we can’t call `join` because we only have a mutable borrow +of each `worker` and `join` takes ownership of its argument. To solve this +issue, we need to move the thread out of the `Worker` instance that owns +`thread` so that `join` can consume the thread. One way to do this is to take +the same approach we took in Listing 18-15. If `Worker` held an +`Option>`, we could call the `take` method on the +`Option` to move the value out of the `Some` variant and leave a `None` variant +in its place. In other words, a `Worker` that is running would have a `Some` +variant in `thread`, and when we wanted to clean up a `Worker`, we’d replace +`Some` with `None` so that the `Worker` wouldn’t have a thread to run. + +However, the *only* time this would come up would be when dropping the +`Worker`. In exchange, we’d have to deal with an +`Option>` anywhere we accessed `worker.thread`. +Idiomatic Rust uses `Option` quite a bit, but when you find yourself wrapping +something you know will always be present in an `Option` as a workaround like +this, it’s a good idea to look for alternative approaches to make your code +cleaner and less error-prone. In this case, a better alternative exists: the `Vec::drain` method. It accepts a range parameter to specify which items to remove from the vector and returns an iterator of those items. Passing the `..` range syntax will remove *every* value from the vector. -So we need to update the `ThreadPool` `drop` implementation like this: +So, we need to update the `ThreadPool` `drop` implementation like this: src/lib.rs @@ -1784,14 +1806,14 @@ This resolves the compiler error and does not require any other changes to our code. Note that, because drop can be called when panicking, the unwrap could also panic and cause a double panic, which immediately crashes the program and ends any cleanup in progress. This is fine for an example program, -but isn’t recommended for production code. +but it isn’t recommended for production code. ### Signaling to the Threads to Stop Listening for Jobs With all the changes we’ve made, our code compiles without any warnings. However, the bad news is that this code doesn’t function the way we want it to yet. The key is the logic in the closures run by the threads of the `Worker` -instances: at the moment, we call `join`, but that won’t shut down the threads, +instances: At the moment, we call `join`, but that won’t shut down the threads, because they `loop` forever looking for jobs. If we try to drop our `ThreadPool` with our current implementation of `drop`, the main thread will block forever, waiting for the first thread to finish. @@ -1799,7 +1821,7 @@ block forever, waiting for the first thread to finish. To fix this problem, we’ll need a change in the `ThreadPool` `drop` implementation and then a change in the `Worker` loop. -First we’ll change the `ThreadPool` `drop` implementation to explicitly drop +First, we’ll change the `ThreadPool` `drop` implementation to explicitly drop the `sender` before waiting for the threads to finish. Listing 21-23 shows the changes to `ThreadPool` to explicitly drop `sender`. Unlike with the thread, here we *do* need to use an `Option` to be able to move `sender` out of @@ -1916,8 +1938,8 @@ The `take` method is defined in the `Iterator` trait and limits the iteration to the first two items at most. The `ThreadPool` will go out of scope at the end of `main`, and the `drop` implementation will run. -Start the server with `cargo run`, and make three requests. The third request -should error, and in your terminal you should see output similar to this: +Start the server with `cargo run` and make three requests. The third request +should error, and in your terminal, you should see output similar to this: ” section). Identifiers are names +as raw identifiers, as we discuss in the [“Raw +Identifiers”][raw-identifiers] section). _Identifiers_ are names of functions, variables, parameters, struct fields, modules, crates, constants, macros, static values, attributes, types, traits, or lifetimes. @@ -14,56 +14,57 @@ macros, static values, attributes, types, traits, or lifetimes. The following is a list of keywords currently in use, with their functionality described. -- `as` - perform primitive casting, disambiguate the specific trait containing - an item, or rename items in `use` statements -- `async` - return a `Future` instead of blocking the current thread -- `await` - suspend execution until the result of a `Future` is ready -- `break` - exit a loop immediately -- `const` - define constant items or constant raw pointers -- `continue` - continue to the next loop iteration -- `crate` - in a module path, refers to the crate root -- `dyn` - dynamic dispatch to a trait object -- `else` - fallback for `if` and `if let` control flow constructs -- `enum` - define an enumeration -- `extern` - link an external function or variable -- `false` - Boolean false literal -- `fn` - define a function or the function pointer type -- `for` - loop over items from an iterator, implement a trait, or specify a - higher-ranked lifetime -- `if` - branch based on the result of a conditional expression -- `impl` - implement inherent or trait functionality -- `in` - part of `for` loop syntax -- `let` - bind a variable -- `loop` - loop unconditionally -- `match` - match a value to patterns -- `mod` - define a module -- `move` - make a closure take ownership of all its captures -- `mut` - denote mutability in references, raw pointers, or pattern bindings -- `pub` - denote public visibility in struct fields, `impl` blocks, or modules -- `ref` - bind by reference -- `return` - return from function -- `Self` - a type alias for the type we are defining or implementing -- `self` - method subject or current module -- `static` - global variable or lifetime lasting the entire program execution -- `struct` - define a structure -- `super` - parent module of the current module -- `trait` - define a trait -- `true` - Boolean true literal -- `type` - define a type alias or associated type -- `union` - define a [union][union]; is only a keyword when used - in a union declaration -- `unsafe` - denote unsafe code, functions, traits, or implementations -- `use` - bring symbols into scope; specify precise captures for generic and - lifetime bounds -- `where` - denote clauses that constrain a type -- `while` - loop conditionally based on the result of an expression +- **`as`**: Perform primitive casting, disambiguate the specific trait + containing an item, or rename items in `use` statements. +- **`async`**: Return a `Future` instead of blocking the current thread. +- **`await`**: Suspend execution until the result of a `Future` is ready. +- **`break`**: Exit a loop immediately. +- **`const`**: Define constant items or constant raw pointers. +- **`continue`**: Continue to the next loop iteration. +- **`crate`**: In a module path, refers to the crate root. +- **`dyn`**: Dynamic dispatch to a trait object. +- **`else`**: Fallback for `if` and `if let` control flow constructs. +- **`enum`**: Define an enumeration. +- **`extern`**: Link an external function or variable. +- **`false`**: Boolean false literal. +- **`fn`**: Define a function or the function pointer type. +- **`for`**: Loop over items from an iterator, implement a trait, or specify a + higher ranked lifetime. +- **`if`**: Branch based on the result of a conditional expression. +- **`impl`**: Implement inherent or trait functionality. +- **`in`**: Part of `for` loop syntax. +- **`let`**: Bind a variable. +- **`loop`**: Loop unconditionally. +- **`match`**: Match a value to patterns. +- **`mod`**: Define a module. +- **`move`**: Make a closure take ownership of all its captures. +- **`mut`**: Denote mutability in references, raw pointers, or pattern bindings. +- **`pub`**: Denote public visibility in struct fields, `impl` blocks, or + modules. +- **`ref`**: Bind by reference. +- **`return`**: Return from function. +- **`Self`**: A type alias for the type we are defining or implementing. +- **`self`**: Method subject or current module. +- **`static`**: Global variable or lifetime lasting the entire program + execution. +- **`struct`**: Define a structure. +- **`super`**: Parent module of the current module. +- **`trait`**: Define a trait. +- **`true`**: Boolean true literal. +- **`type`**: Define a type alias or associated type. +- **`union`**: Define a [union][union]; is a keyword only when + used in a union declaration. +- **`unsafe`**: Denote unsafe code, functions, traits, or implementations. +- **`use`**: Bring symbols into scope. +- **`where`**: Denote clauses that constrain a type. +- **`while`**: Loop conditionally based on the result of an expression. [union]: ../reference/items/unions.html ### Keywords Reserved for Future Use The following keywords do not yet have any functionality but are reserved by -Rust for potential future use. +Rust for potential future use: - `abstract` - `become` diff --git a/src/appendix-02-operators.md b/src/appendix-02-operators.md index 36482f8486..e938b5e531 100644 --- a/src/appendix-02-operators.md +++ b/src/appendix-02-operators.md @@ -39,7 +39,7 @@ overload that operator is listed. | `->` | `fn(...) -> type`, |...| -> type | Function and closure return type | | | `.` | `expr.ident` | Field access | | | `.` | `expr.ident(expr, ...)` | Method call | | -| `.` | `expr.0`, `expr.1`, etc. | Tuple indexing | | +| `.` | `expr.0`, `expr.1`, and so on | Tuple indexing | | | `..` | `..`, `expr..`, `..expr`, `expr..expr` | Right-exclusive range literal | `PartialOrd` | | `..=` | `..=expr`, `expr..=expr` | Right-inclusive range literal | `PartialOrd` | | `..` | `..expr` | Struct literal update syntax | | @@ -74,26 +74,26 @@ overload that operator is listed. ### Non-operator Symbols -The following list contains all symbols that don’t function as operators; that +The following tables contain all symbols that don’t function as operators; that is, they don’t behave like a function or method call. Table B-2 shows symbols that appear on their own and are valid in a variety of locations. -Table B-2: Stand-Alone Syntax +Table B-2: Stand-alone Syntax | Symbol | Explanation | | ---------------------------------------------------------------------- | ---------------------------------------------------------------------- | | `'ident` | Named lifetime or loop label | -| Digits immediately followed by `u8`, `i32`, `f64`, `usize`, and so on | Numeric literal of specific type | +| Digits immediately followed by `u8`, `i32`, `f64`, `usize`, and so on | Numeric literal of specific type | | `"..."` | String literal | -| `r"..."`, `r#"..."#`, `r##"..."##`, etc. | Raw string literal, escape characters not processed | +| `r"..."`, `r#"..."#`, `r##"..."##`, and so on | Raw string literal; escape characters not processed | | `b"..."` | Byte string literal; constructs an array of bytes instead of a string | -| `br"..."`, `br#"..."#`, `br##"..."##`, etc. | Raw byte string literal, combination of raw and byte string literal | +| `br"..."`, `br#"..."#`, `br##"..."##`, and so on | Raw byte string literal; combination of raw and byte string literal | | `'...'` | Character literal | | `b'...'` | ASCII byte literal | | |...| expr | Closure | -| `!` | Always empty bottom type for diverging functions | +| `!` | Always-empty bottom type for diverging functions | | `_` | “Ignored” pattern binding; also used to make integer literals readable | Table B-3 shows symbols that appear in the context of a path through the module @@ -101,33 +101,33 @@ hierarchy to an item. Table B-3: Path-Related Syntax -| Symbol | Explanation | -| --------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | -| `ident::ident` | Namespace path | -| `::path` | Path relative to the extern prelude, where all other crates are rooted (i.e., an explicitly absolute path including crate name) | -| `self::path` | Path relative to the current module (i.e., an explicitly relative path). | -| `super::path` | Path relative to the parent of the current module | -| `type::ident`, `::ident` | Associated constants, functions, and types | -| `::...` | Associated item for a type that cannot be directly named (e.g., `<&T>::...`, `<[T]>::...`, etc.) | -| `trait::method(...)` | Disambiguating a method call by naming the trait that defines it | -| `type::method(...)` | Disambiguating a method call by naming the type for which it’s defined | -| `::method(...)` | Disambiguating a method call by naming the trait and type | +| Symbol | Explanation | +| --------------------------------------- | -------------------------------------------------------------------------------------------------------------| +| `ident::ident` | Namespace path | +| `::path` | Path relative to the crate root (that is, an explicitly absolute path) | +| `self::path` | Path relative to the current module (that is, an explicitly relative path) | +| `super::path` | Path relative to the parent of the current module | +| `type::ident`, `::ident` | Associated constants, functions, and types | +| `::...` | Associated item for a type that cannot be directly named (for example, `<&T>::...`, `<[T]>::...`, and so on) | +| `trait::method(...)` | Disambiguating a method call by naming the trait that defines it | +| `type::method(...)` | Disambiguating a method call by naming the type for which it’s defined | +| `::method(...)` | Disambiguating a method call by naming the trait and type | Table B-4 shows symbols that appear in the context of using generic type parameters. Table B-4: Generics -| Symbol | Explanation | -| ------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------- | -| `path<...>` | Specifies parameters to generic type in a type (e.g., `Vec`) | -| `path::<...>`, `method::<...>` | Specifies parameters to generic type, function, or method in an expression; often referred to as turbofish (e.g., `"42".parse::()`) | -| `fn ident<...> ...` | Define generic function | -| `struct ident<...> ...` | Define generic structure | -| `enum ident<...> ...` | Define generic enumeration | -| `impl<...> ...` | Define generic implementation | -| `for<...> type` | Higher-ranked lifetime bounds | -| `type` | A generic type where one or more associated types have specific assignments (e.g., `Iterator`) | +| Symbol | Explanation | +| ------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------- | +| `path<...>` | Specifies parameters to a generic type in a type (for example, `Vec`) | +| `path::<...>`, `method::<...>` | Specifies parameters to a generic type, function, or method in an expression; often referred to as _turbofish_ (for example, `"42".parse::()`) | +| `fn ident<...> ...` | Define generic function | +| `struct ident<...> ...` | Define generic structure | +| `enum ident<...> ...` | Define generic enumeration | +| `impl<...> ...` | Define generic implementation | +| `for<...> type` | Higher ranked lifetime bounds | +| `type` | A generic type where one or more associated types have specific assignments (for example, `Iterator`) | Table B-5 shows symbols that appear in the context of constraining generic type parameters with trait bounds. @@ -184,7 +184,7 @@ Table B-8 shows the contexts in which parentheses are used. | `(type, ...)` | Tuple type | | `expr(expr, ...)` | Function call expression; also used to initialize tuple `struct`s and tuple `enum` variants | -Table B-9 shows the contexts in which curly braces are used. +Table B-9 shows the contexts in which curly brackets are used. Table B-9: Curly Brackets @@ -202,5 +202,5 @@ Table B-10 shows the contexts in which square brackets are used. | `[...]` | Array literal | | `[expr; len]` | Array literal containing `len` copies of `expr` | | `[type; len]` | Array type containing `len` instances of `type` | -| `expr[expr]` | Collection indexing. Overloadable (`Index`, `IndexMut`) | +| `expr[expr]` | Collection indexing; overloadable (`Index`, `IndexMut`) | | `expr[..]`, `expr[a..]`, `expr[..b]`, `expr[a..b]` | Collection indexing pretending to be collection slicing, using `Range`, `RangeFrom`, `RangeTo`, or `RangeFull` as the “index” | diff --git a/src/appendix-03-derivable-traits.md b/src/appendix-03-derivable-traits.md index 8e74b948c2..1bcc1de931 100644 --- a/src/appendix-03-derivable-traits.md +++ b/src/appendix-03-derivable-traits.md @@ -31,10 +31,10 @@ would be most relevant to them? The Rust compiler doesn’t have this insight, s it can’t provide appropriate default behavior for you. The list of derivable traits provided in this appendix is not comprehensive: -libraries can implement `derive` for their own traits, making the list of -traits you can use `derive` with truly open-ended. Implementing `derive` -involves using a procedural macro, which is covered in the -[“Macros”][macros] section of Chapter 20. +Libraries can implement `derive` for their own traits, making the list of +traits you can use `derive` with truly open ended. Implementing `derive` +involves using a procedural macro, which is covered in the [“Custom `derive` +Macros”][custom-derive-macros] section in Chapter 20. ### `Debug` for Programmer Output @@ -47,8 +47,8 @@ at a particular point in a program’s execution. The `Debug` trait is required, for example, in the use of the `assert_eq!` macro. This macro prints the values of instances given as arguments if the -equality assertion fails so programmers can see why the two instances weren’t -equal. +equality assertion fails so that programmers can see why the two instances +weren’t equal. ### `PartialEq` and `Eq` for Equality Comparisons @@ -57,7 +57,7 @@ equality and enables use of the `==` and `!=` operators. Deriving `PartialEq` implements the `eq` method. When `PartialEq` is derived on structs, two instances are equal only if _all_ fields are equal, and the -instances are not equal if any fields are not equal. When derived on enums, +instances are not equal if _any_ fields are not equal. When derived on enums, each variant is equal to itself and not equal to the other variants. The `PartialEq` trait is required, for example, with the use of the @@ -67,12 +67,12 @@ for equality. The `Eq` trait has no methods. Its purpose is to signal that for every value of the annotated type, the value is equal to itself. The `Eq` trait can only be applied to types that also implement `PartialEq`, although not all types that -implement `PartialEq` can implement `Eq`. One example of this is floating point -number types: the implementation of floating point numbers states that two +implement `PartialEq` can implement `Eq`. One example of this is floating-point +number types: The implementation of floating-point numbers states that two instances of the not-a-number (`NaN`) value are not equal to each other. -An example of when `Eq` is required is for keys in a `HashMap` so the -`HashMap` can tell whether two keys are the same. +An example of when `Eq` is required is for keys in a `HashMap` so that +the `HashMap` can tell whether two keys are the same. ### `PartialOrd` and `Ord` for Ordering Comparisons @@ -84,8 +84,8 @@ that also implement `PartialEq`. Deriving `PartialOrd` implements the `partial_cmp` method, which returns an `Option` that will be `None` when the values given don’t produce an ordering. An example of a value that doesn’t produce an ordering, even though -most values of that type can be compared, is the not-a-number (`NaN`) floating -point value. Calling `partial_cmp` with any floating-point number and the `NaN` +most values of that type can be compared, is the `NaN` floating point value. +Calling `partial_cmp` with any floating-point number and the `NaN` floating-point value will return `None`. When derived on structs, `PartialOrd` compares two instances by comparing the @@ -112,9 +112,9 @@ a data structure that stores data based on the sort order of the values. The `Clone` trait allows you to explicitly create a deep copy of a value, and the duplication process might involve running arbitrary code and copying heap -data. See [Variables and Data Interacting with -Clone”][variables-and-data-interacting-with-clone] in Chapter 4 -for more information on `Clone`. +data. See the [“Variables and Data Interacting with +Clone”][variables-and-data-interacting-with-clone] section in +Chapter 4 for more information on `Clone`. Deriving `Clone` implements the `clone` method, which when implemented for the whole type, calls `clone` on each of the parts of the type. This means all the @@ -123,12 +123,12 @@ fields or values in the type must also implement `Clone` to derive `Clone`. An example of when `Clone` is required is when calling the `to_vec` method on a slice. The slice doesn’t own the type instances it contains, but the vector returned from `to_vec` will need to own its instances, so `to_vec` calls -`clone` on each item. Thus the type stored in the slice must implement `Clone`. +`clone` on each item. Thus, the type stored in the slice must implement `Clone`. The `Copy` trait allows you to duplicate a value by only copying bits stored on -the stack; no arbitrary code is necessary. See [“Stack-Only Data: -Copy”][stack-only-data-copy] in Chapter 4 for more information on -`Copy`. +the stack; no arbitrary code is necessary. See the [“Stack-Only Data: +Copy”][stack-only-data-copy] section in Chapter 4 for more +information on `Copy`. The `Copy` trait doesn’t define any methods to prevent programmers from overloading those methods and violating the assumption that no arbitrary code @@ -136,7 +136,7 @@ is being run. That way, all programmers can assume that copying a value will be very fast. You can derive `Copy` on any type whose parts all implement `Copy`. A type that -implements `Copy` must also implement `Clone`, because a type that implements +implements `Copy` must also implement `Clone` because a type that implements `Copy` has a trivial implementation of `Clone` that performs the same task as `Copy`. @@ -167,11 +167,11 @@ meaning all fields or values in the type must also implement `Default` to derive `Default`. The `Default::default` function is commonly used in combination with the struct -update syntax discussed in [“Creating Instances from Other Instances with Struct -Update +update syntax discussed in the [“Creating Instances from Other Instances with +Struct Update Syntax”][creating-instances-from-other-instances-with-struct-update-syntax] in Chapter 5. You can customize a few fields of a struct and then set -and use a default value for the rest of the fields by using +ignore --> section in Chapter 5. You can customize a few fields of a struct and +then set and use a default value for the rest of the fields by using `..Default::default()`. The `Default` trait is required when you use the method `unwrap_or_default` on @@ -182,4 +182,4 @@ The `Default` trait is required when you use the method `unwrap_or_default` on [creating-instances-from-other-instances-with-struct-update-syntax]: ch05-01-defining-structs.html#creating-instances-from-other-instances-with-struct-update-syntax [stack-only-data-copy]: ch04-01-what-is-ownership.html#stack-only-data-copy [variables-and-data-interacting-with-clone]: ch04-01-what-is-ownership.html#variables-and-data-interacting-with-clone -[macros]: ch20-05-macros.html#macros +[custom-derive-macros]: ch20-05-macros.html#custom-derive-macros diff --git a/src/appendix-04-useful-development-tools.md b/src/appendix-04-useful-development-tools.md index 02dd6490cb..6719eaa26d 100644 --- a/src/appendix-04-useful-development-tools.md +++ b/src/appendix-04-useful-development-tools.md @@ -1,4 +1,4 @@ -## Appendix D - Useful Development Tools +## Appendix D: Useful Development Tools In this appendix, we talk about some useful development tools that the Rust project provides. We’ll look at automatic formatting, quick ways to apply @@ -8,11 +8,11 @@ warning fixes, a linter, and integrating with IDEs. The `rustfmt` tool reformats your code according to the community code style. Many collaborative projects use `rustfmt` to prevent arguments about which -style to use when writing Rust: everyone formats their code using the tool. +style to use when writing Rust: Everyone formats their code using the tool. Rust installations include `rustfmt` by default, so you should already have the programs `rustfmt` and `cargo-fmt` on your system. These two commands are -analogous to `rustc` and `cargo` in that `rustfmt` allows finer-grained control +analogous to `rustc` and `cargo` in that `rustfmt` allows finer grained control and `cargo-fmt` understands conventions of a project that uses Cargo. To format any Cargo project, enter the following: @@ -26,10 +26,10 @@ on `rustfmt`, see [its documentation][rustfmt]. ### Fix Your Code with `rustfix` -The `rustfix` tool is included with Rust installations and can automatically fix -compiler warnings that have a clear way to correct the problem that’s likely -what you want. You’ve probably seen compiler warnings before. For example, -consider this code: +The `rustfix` tool is included with Rust installations and can automatically +fix compiler warnings that have a clear way to correct the problem that’s +likely what you want. You’ve probably seen compiler warnings before. For +example, consider this code: Filename: src/main.rs @@ -83,13 +83,14 @@ fn main() { The variable `x` is now immutable, and the warning no longer appears. You can also use the `cargo fix` command to transition your code between -different Rust editions. Editions are covered in [Appendix E][editions]. +different Rust editions. Editions are covered in [Appendix E][editions]. ### More Lints with Clippy -The Clippy tool is a collection of lints to analyze your code so you can catch -common mistakes and improve your Rust code. Clippy is included with standard -Rust installations. +The Clippy tool is a collection of lints to analyze your code so that you can +catch common mistakes and improve your Rust code. Clippy is included with +standard Rust installations. To run Clippy’s lints on any Cargo project, enter the following: diff --git a/src/appendix-05-editions.md b/src/appendix-05-editions.md index 0b8c4a3a12..ef210ce6a4 100644 --- a/src/appendix-05-editions.md +++ b/src/appendix-05-editions.md @@ -1,4 +1,4 @@ -## Appendix E - Editions +## Appendix E: Editions In Chapter 1, you saw that `cargo new` adds a bit of metadata to your _Cargo.toml_ file about an edition. This appendix talks about what that means! @@ -46,12 +46,14 @@ Rust 2018, your project will compile and be able to use that dependency. The opposite situation, where your project uses Rust 2018 and a dependency uses Rust 2015, works as well. -To be clear: most features will be available on all editions. Developers using +To be clear: Most features will be available on all editions. Developers using any Rust edition will continue to see improvements as new stable releases are made. However, in some cases, mainly when new keywords are added, some new features might only be available in later editions. You will need to switch editions if you want to take advantage of such features. -For more details, the [_Edition Guide_](https://doc.rust-lang.org/stable/edition-guide/) is a complete book -about editions that enumerates the differences between editions and explains -how to automatically upgrade your code to a new edition via `cargo fix`. +For more details, see [_The Rust Edition Guide_][edition-guide]. This is a +complete book that enumerates the differences between editions and explains how +to automatically upgrade your code to a new edition via `cargo fix`. + +[edition-guide]: https://doc.rust-lang.org/stable/edition-guide diff --git a/src/ch10-00-generics.md b/src/ch10-00-generics.md index 20289557f1..7e1055fdc6 100644 --- a/src/ch10-00-generics.md +++ b/src/ch10-00-generics.md @@ -8,25 +8,25 @@ when compiling and running the code. Functions can take parameters of some generic type, instead of a concrete type like `i32` or `String`, in the same way they take parameters with unknown -values to run the same code on multiple concrete values. In fact, we’ve already +values to run the same code on multiple concrete values. In fact, we already used generics in Chapter 6 with `Option`, in Chapter 8 with `Vec` and `HashMap`, and in Chapter 9 with `Result`. In this chapter, you’ll explore how to define your own types, functions, and methods with generics! -First we’ll review how to extract a function to reduce code duplication. We’ll +First, we’ll review how to extract a function to reduce code duplication. We’ll then use the same technique to make a generic function from two functions that differ only in the types of their parameters. We’ll also explain how to use generic types in struct and enum definitions. -Then you’ll learn how to use _traits_ to define behavior in a generic way. You +Then, you’ll learn how to use traits to define behavior in a generic way. You can combine traits with generic types to constrain a generic type to accept only those types that have a particular behavior, as opposed to just any type. Finally, we’ll discuss _lifetimes_: a variety of generics that give the compiler information about how references relate to each other. Lifetimes allow us to give the compiler enough information about borrowed values so that it can -ensure references will be valid in more situations than it could without our -help. +ensure that references will be valid in more situations than it could without +our help. ## Removing Duplication by Extracting a Function @@ -34,7 +34,7 @@ Generics allow us to replace specific types with a placeholder that represents multiple types to remove code duplication. Before diving into generics syntax, let’s first look at how to remove duplication in a way that doesn’t involve generic types by extracting a function that replaces specific values with a -placeholder that represents multiple values. Then we’ll apply the same +placeholder that represents multiple values. Then, we’ll apply the same technique to extract a generic function! By looking at how to recognize duplicated code you can extract into a function, you’ll start to recognize duplicated code that can use generics. @@ -71,7 +71,7 @@ the same logic at two different places in the program, as shown in Listing 10-2. -Although this code works, duplicating code is tedious and error prone. We also +Although this code works, duplicating code is tedious and error-prone. We also have to remember to update the code in multiple places when we want to change it. @@ -81,7 +81,7 @@ solution makes our code clearer and lets us express the concept of finding the largest number in a list abstractly. In Listing 10-3, we extract the code that finds the largest number into a -function named `largest`. Then we call the function to find the largest number +function named `largest`. Then, we call the function to find the largest number in the two lists from Listing 10-2. We could also use the function on any other list of `i32` values we might have in the future. diff --git a/src/ch10-01-syntax.md b/src/ch10-01-syntax.md index 9be7e005a1..8a13a252e0 100644 --- a/src/ch10-01-syntax.md +++ b/src/ch10-01-syntax.md @@ -3,7 +3,7 @@ We use generics to create definitions for items like function signatures or structs, which we can then use with many different concrete data types. Let’s first look at how to define functions, structs, enums, and methods using -generics. Then we’ll discuss how generics affect code performance. +generics. Then, we’ll discuss how generics affect code performance. ### In Function Definitions @@ -33,13 +33,13 @@ To parameterize the types in a new single function, we need to name the type parameter, just as we do for the value parameters to a function. You can use any identifier as a type parameter name. But we’ll use `T` because, by convention, type parameter names in Rust are short, often just one letter, and -Rust’s type-naming convention is CamelCase. Short for _type_, `T` is the default -choice of most Rust programmers. +Rust’s type-naming convention is UpperCamelCase. Short for _type_, `T` is the +default choice of most Rust programmers. When we use a parameter in the body of the function, we have to declare the -parameter name in the signature so the compiler knows what that name means. -Similarly, when we use a type parameter name in a function signature, we have -to declare the type parameter name before we use it. To define the generic +parameter name in the signature so that the compiler knows what that name +means. Similarly, when we use a type parameter name in a function signature, we +have to declare the type parameter name before we use it. To define the generic `largest` function, we place type name declarations inside angle brackets, `<>`, between the name of the function and the parameter list, like this: @@ -47,8 +47,8 @@ to declare the type parameter name before we use it. To define the generic fn largest(list: &[T]) -> &T { ``` -We read this definition as: the function `largest` is generic over some type -`T`. This function has one parameter named `list`, which is a slice of values +We read this definition as “The function `largest` is generic over some type +`T`.” This function has one parameter named `list`, which is a slice of values of type `T`. The `largest` function will return a reference to a value of the same type `T`. @@ -71,7 +71,7 @@ If we compile this code right now, we’ll get this error: {{#include ../listings/ch10-generic-types-traits-and-lifetimes/listing-10-05/output.txt}} ``` -The help text mentions `std::cmp::PartialOrd`, which is a _trait_, and we’re +The help text mentions `std::cmp::PartialOrd`, which is a trait, and we’re going to talk about traits in the next section. For now, know that this error states that the body of `largest` won’t work for all possible types that `T` could be. Because we want to compare values of type `T` in the body, we can @@ -97,10 +97,9 @@ fields using the `<>` syntax. Listing 10-6 defines a `Point` struct to hold The syntax for using generics in struct definitions is similar to that used in -function definitions. First we declare the name of the type parameter inside -angle brackets just after the name of the struct. Then we use the generic -type in the struct definition where we would otherwise specify concrete data -types. +function definitions. First, we declare the name of the type parameter inside +angle brackets just after the name of the struct. Then, we use the generic type +in the struct definition where we would otherwise specify concrete data types. Note that because we’ve used only one generic type to define `Point`, this definition says that the `Point` struct is generic over some type `T`, and @@ -118,8 +117,8 @@ Listing 10-7, our code won’t compile. In this example, when we assign the integer value `5` to `x`, we let the compiler know that the generic type `T` will be an integer for this instance of -`Point`. Then when we specify `4.0` for `y`, which we’ve defined to have the -same type as `x`, we’ll get a type mismatch error like this: +`Point`. Then, when we specify `4.0` for `y`, which we’ve defined to have +the same type as `x`, we’ll get a type mismatch error like this: ```console {{#include ../listings/ch10-generic-types-traits-and-lifetimes/listing-10-07/output.txt}} @@ -204,11 +203,11 @@ struct we defined in Listing 10-6 with a method named `x` implemented on it. Here, we’ve defined a method named `x` on `Point` that returns a reference to the data in the field `x`. -Note that we have to declare `T` just after `impl` so we can use `T` to specify -that we’re implementing methods on the type `Point`. By declaring `T` as a -generic type after `impl`, Rust can identify that the type in the angle -brackets in `Point` is a generic type rather than a concrete type. We could -have chosen a different name for this generic parameter than the generic +Note that we have to declare `T` just after `impl` so that we can use `T` to +specify that we’re implementing methods on the type `Point`. By declaring +`T` as a generic type after `impl`, Rust can identify that the type in the +angle brackets in `Point` is a generic type rather than a concrete type. We +could have chosen a different name for this generic parameter than the generic parameter declared in the struct definition, but using the same name is conventional. If you write a method within an `impl` that declares a generic type, that method will be defined on any instance of the type, no matter what @@ -216,7 +215,7 @@ concrete type ends up substituting for the generic type. We can also specify constraints on generic types when defining methods on the type. We could, for example, implement methods only on `Point` instances -rather than on `Point` instances with any generic type. In Listing 10-10 we +rather than on `Point` instances with any generic type. In Listing 10-10, we use the concrete type `f32`, meaning we don’t declare any types after `impl`. @@ -235,12 +234,12 @@ available only for floating-point types. Generic type parameters in a struct definition aren’t always the same as those you use in that same struct’s method signatures. Listing 10-11 uses the generic -types `X1` and `Y1` for the `Point` struct and `X2` `Y2` for the `mixup` method -signature to make the example clearer. The method creates a new `Point` +types `X1` and `Y1` for the `Point` struct and `X2` and `Y2` for the `mixup` +method signature to make the example clearer. The method creates a new `Point` instance with the `x` value from the `self` `Point` (of type `X1`) and the `y` value from the passed-in `Point` (of type `Y2`). -+ ```rust {{#rustdoc_include ../listings/ch10-generic-types-traits-and-lifetimes/listing-10-11/src/main.rs}} @@ -273,7 +272,7 @@ Rust accomplishes this by performing monomorphization of the code using generics at compile time. _Monomorphization_ is the process of turning generic code into specific code by filling in the concrete types that are used when compiled. In this process, the compiler does the opposite of the steps we used -to create the generic function in Listing 10-5: the compiler looks at all the +to create the generic function in Listing 10-5: The compiler looks at all the places where generic code is called and generates code for the concrete types the generic code is called with. @@ -287,7 +286,7 @@ let float = Some(5.0); When Rust compiles this code, it performs monomorphization. During that process, the compiler reads the values that have been used in `Option` -instances and identifies two kinds of `Option`: one is `i32` and the other +instances and identifies two kinds of `Option`: One is `i32` and the other is `f64`. As such, it expands the generic definition of `Option` into two definitions specialized to `i32` and `f64`, thereby replacing the generic definition with the specific ones. diff --git a/src/ch10-02-traits.md b/src/ch10-02-traits.md index 952266b661..1698cda0d3 100644 --- a/src/ch10-02-traits.md +++ b/src/ch10-02-traits.md @@ -53,7 +53,7 @@ its own custom behavior for the body of the method. The compiler will enforce that any type that has the `Summary` trait will have the method `summarize` defined with this signature exactly. -A trait can have multiple methods in its body: the method signatures are listed +A trait can have multiple methods in its body: The method signatures are listed one per line, and each line ends in a semicolon. ### Implementing a Trait on a Type @@ -107,14 +107,14 @@ library traits like `Display` on a custom type like `SocialPost` as part of our crate. But we can’t implement external traits on external types. For example, we can’t -implement the `Display` trait on `Vec` within our `aggregator` crate because -`Display` and `Vec` are both defined in the standard library and aren’t -local to our `aggregator` crate. This restriction is part of a property called -_coherence_, and more specifically the _orphan rule_, so named because the -parent type is not present. This rule ensures that other people’s code can’t -break your code and vice versa. Without the rule, two crates could implement -the same trait for the same type, and Rust wouldn’t know which implementation -to use. +implement the `Display` trait on `Vec` within our `aggregator` crate, +because `Display` and `Vec` are both defined in the standard library and +aren’t local to our `aggregator` crate. This restriction is part of a property +called _coherence_, and more specifically the _orphan rule_, so named because +the parent type is not present. This rule ensures that other people’s code +can’t break your code and vice versa. Without the rule, two crates could +implement the same trait for the same type, and Rust wouldn’t know which +implementation to use. @@ -216,7 +216,7 @@ keyword and the trait name. This parameter accepts any type that implements the specified trait. In the body of `notify`, we can call any methods on `item` that come from the `Summary` trait, such as `summarize`. We can call `notify` and pass in any instance of `NewsArticle` or `SocialPost`. Code that calls the -function with any other type, such as a `String` or an `i32`, won’t compile +function with any other type, such as a `String` or an `i32`, won’t compile, because those types don’t implement `Summary`. @@ -267,7 +267,7 @@ passed as an argument for `item1` and `item2` must be the same. #### Multiple Trait Bounds with the `+` Syntax We can also specify more than one trait bound. Say we wanted `notify` to use -display formatting as well as `summarize` on `item`: we specify in the `notify` +display formatting as well as `summarize` on `item`: We specify in the `notify` definition that `item` must implement both `Display` and `Summary`. We can do so using the `+` syntax: @@ -303,7 +303,7 @@ we can use a `where` clause, like this: {{#rustdoc_include ../listings/ch10-generic-types-traits-and-lifetimes/no-listing-07-where-clause/src/lib.rs:here}} ``` -This function’s signature is less cluttered: the function name, parameter list, +This function’s signature is less cluttered: The function name, parameter list, and return type are close together, similar to a function without lots of trait bounds. @@ -340,21 +340,20 @@ the return type specified as `impl Summary` wouldn’t work: Returning either a `NewsArticle` or a `SocialPost` isn’t allowed due to restrictions around how the `impl Trait` syntax is implemented in the compiler. We’ll cover how to write a function with this behavior in the [“Using Trait -Objects That Allow for Values of Different -Types”][using-trait-objects-that-allow-for-values-of-different-types] section of Chapter 18. +Objects to Abstract over Shared Behavior”][trait-objects] +section of Chapter 18. ### Using Trait Bounds to Conditionally Implement Methods By using a trait bound with an `impl` block that uses generic type parameters, we can implement methods conditionally for types that implement the specified traits. For example, the type `Pair` in Listing 10-15 always implements the -`new` function to return a new instance of `Pair` (recall from the -[“Defining Methods”][methods] section of Chapter 5 that `Self` -is a type alias for the type of the `impl` block, which in this case is -`Pair`). But in the next `impl` block, `Pair` only implements the -`cmp_display` method if its inner type `T` implements the `PartialOrd` trait -that enables comparison _and_ the `Display` trait that enables printing. +`new` function to return a new instance of `Pair` (recall from the [“Method +Syntax”][methods] section of Chapter 5 that `Self` is a type +alias for the type of the `impl` block, which in this case is `Pair`). But +in the next `impl` block, `Pair` only implements the `cmp_display` method if +its inner type `T` implements the `PartialOrd` trait that enables comparison +_and_ the `Display` trait that enables printing. @@ -394,12 +393,12 @@ reduce duplication but also specify to the compiler that we want the generic type to have particular behavior. The compiler can then use the trait bound information to check that all the concrete types used with our code provide the correct behavior. In dynamically typed languages, we would get an error at -runtime if we called a method on a type which didn’t define the method. But -Rust moves these errors to compile time so we’re forced to fix the problems +runtime if we called a method on a type that didn’t define the method. But Rust +moves these errors to compile time so that we’re forced to fix the problems before our code is even able to run. Additionally, we don’t have to write code -that checks for behavior at runtime because we’ve already checked at compile +that checks for behavior at runtime, because we’ve already checked at compile time. Doing so improves performance without having to give up the flexibility of generics. -[using-trait-objects-that-allow-for-values-of-different-types]: ch18-02-trait-objects.html#using-trait-objects-that-allow-for-values-of-different-types -[methods]: ch05-03-method-syntax.html#defining-methods +[trait-objects]: ch18-02-trait-objects.html#using-trait-objects-to-abstract-over-shared-behavior +[methods]: ch05-03-method-syntax.html#method-syntax diff --git a/src/ch10-03-lifetime-syntax.md b/src/ch10-03-lifetime-syntax.md index b96aa617b8..fc38d74151 100644 --- a/src/ch10-03-lifetime-syntax.md +++ b/src/ch10-03-lifetime-syntax.md @@ -6,19 +6,19 @@ references are valid as long as we need them to be. One detail we didn’t discuss in the [“References and Borrowing”][references-and-borrowing] section in Chapter 4 is -that every reference in Rust has a _lifetime_, which is the scope for which +that every reference in Rust has a lifetime, which is the scope for which that reference is valid. Most of the time, lifetimes are implicit and inferred, just like most of the time, types are inferred. We are only required to -annotate types when multiple types are possible. In a similar way, we have to +annotate types when multiple types are possible. In a similar way, we must annotate lifetimes when the lifetimes of references could be related in a few different ways. Rust requires us to annotate the relationships using generic -lifetime parameters to ensure the actual references used at runtime will +lifetime parameters to ensure that the actual references used at runtime will definitely be valid. Annotating lifetimes is not even a concept most other programming languages have, so this is going to feel unfamiliar. Although we won’t cover lifetimes in their entirety in this chapter, we’ll discuss common ways you might encounter -lifetime syntax so you can get comfortable with the concept. +lifetime syntax so that you can get comfortable with the concept. @@ -26,10 +26,10 @@ lifetime syntax so you can get comfortable with the concept. ### Dangling References -The main aim of lifetimes is to prevent _dangling references_, which cause a -program to reference data other than the data it’s intended to reference. -Consider the program in Listing 10-16, which has an outer scope and an inner -scope. +The main aim of lifetimes is to prevent dangling references, which, if they +were allowed to exist, would cause a program to reference data other than the +data it’s intended to reference. Consider the program in Listing 10-16, which +has an outer scope and an inner scope. @@ -41,17 +41,17 @@ scope. > Note: The examples in Listings 10-16, 10-17, and 10-23 declare variables > without giving them an initial value, so the variable name exists in the outer -> scope. At first glance, this might appear to be in conflict with Rust’s having +> scope. At first glance, this might appear to be in conflict with Rust having > no null values. However, if we try to use a variable before giving it a value, -> we’ll get a compile-time error, which shows that Rust indeed does not allow +> we’ll get a compile-time error, which shows that indeed Rust does not allow > null values. The outer scope declares a variable named `r` with no initial value, and the inner scope declares a variable named `x` with the initial value of `5`. Inside -the inner scope, we attempt to set the value of `r` as a reference to `x`. Then -the inner scope ends, and we attempt to print the value in `r`. This code won’t -compile because the value that `r` is referring to has gone out of scope before -we try to use it. Here is the error message: +the inner scope, we attempt to set the value of `r` as a reference to `x`. +Then, the inner scope ends, and we attempt to print the value in `r`. This code +won’t compile, because the value that `r` is referring to has gone out of scope +before we try to use it. Here is the error message: ```console {{#include ../listings/ch10-generic-types-traits-and-lifetimes/listing-10-16/output.txt}} @@ -62,7 +62,7 @@ reason is that `x` will be out of scope when the inner scope ends on line 7. But `r` is still valid for the outer scope; because its scope is larger, we say that it “lives longer.” If Rust allowed this code to work, `r` would be referencing memory that was deallocated when `x` went out of scope, and -anything we tried to do with `r` wouldn’t work correctly. So how does Rust +anything we tried to do with `r` wouldn’t work correctly. So, how does Rust determine that this code is invalid? It uses a borrow checker. ### The Borrow Checker @@ -84,10 +84,10 @@ with `'b`. As you can see, the inner `'b` block is much smaller than the outer `'a` lifetime block. At compile time, Rust compares the size of the two lifetimes and sees that `r` has a lifetime of `'a` but that it refers to memory with a lifetime of `'b`. The program is rejected because `'b` is shorter than -`'a`: the subject of the reference doesn’t live as long as the reference. +`'a`: The subject of the reference doesn’t live as long as the reference. -Listing 10-18 fixes the code so it doesn’t have a dangling reference and it -compiles without any errors. +Listing 10-18 fixes the code so that it doesn’t have a dangling reference and +it compiles without any errors. @@ -102,8 +102,8 @@ means `r` can reference `x` because Rust knows that the reference in `r` will always be valid while `x` is valid. Now that you know where the lifetimes of references are and how Rust analyzes -lifetimes to ensure references will always be valid, let’s explore generic -lifetimes of parameters and return values in the context of functions. +lifetimes to ensure that references will always be valid, let’s explore generic +lifetimes in function parameters and return values. ### Generic Lifetimes in Functions @@ -158,7 +158,7 @@ Listings 10-17 and 10-18 to determine whether the reference we return will always be valid. The borrow checker can’t determine this either, because it doesn’t know how the lifetimes of `x` and `y` relate to the lifetime of the return value. To fix this error, we’ll add generic lifetime parameters that -define the relationship between the references so the borrow checker can +define the relationship between the references so that the borrow checker can perform its analysis. ### Lifetime Annotation Syntax @@ -169,15 +169,15 @@ other without affecting the lifetimes. Just as functions can accept any type when the signature specifies a generic type parameter, functions can accept references with any lifetime by specifying a generic lifetime parameter. -Lifetime annotations have a slightly unusual syntax: the names of lifetime +Lifetime annotations have a slightly unusual syntax: The names of lifetime parameters must start with an apostrophe (`'`) and are usually all lowercase and very short, like generic types. Most people use the name `'a` for the first lifetime annotation. We place lifetime parameter annotations after the `&` of a reference, using a space to separate the annotation from the reference’s type. -Here are some examples: a reference to an `i32` without a lifetime parameter, a +Here are some examples—a reference to an `i32` without a lifetime parameter, a reference to an `i32` that has a lifetime parameter named `'a`, and a mutable -reference to an `i32` that also has the lifetime `'a`. +reference to an `i32` that also has the lifetime `'a`: ```rust,ignore &i32 // a reference @@ -185,7 +185,7 @@ reference to an `i32` that also has the lifetime `'a`. &'a mut i32 // a mutable reference with an explicit lifetime ``` -One lifetime annotation by itself doesn’t have much meaning because the +One lifetime annotation by itself doesn’t have much meaning, because the annotations are meant to tell Rust how generic lifetime parameters of multiple references relate to each other. Let’s examine how the lifetime annotations relate to each other in the context of the `longest` function. @@ -197,14 +197,14 @@ relate to each other in the context of the `longest` function. ### In Function Signatures To use lifetime annotations in function signatures, we need to declare the -generic _lifetime_ parameters inside angle brackets between the function name -and the parameter list, just as we did with generic _type_ parameters. +generic lifetime parameters inside angle brackets between the function name and +the parameter list, just as we did with generic type parameters. -We want the signature to express the following constraint: the returned -reference will be valid as long as both the parameters are valid. This is the -relationship between lifetimes of the parameters and the return value. We’ll -name the lifetime `'a` and then add it to each reference, as shown in Listing -10-21. +We want the signature to express the following constraint: The returned +reference will be valid as long as both of the parameters are valid. This is +the relationship between lifetimes of the parameters and the return value. +We’ll name the lifetime `'a` and then add it to each reference, as shown in +Listing 10-21. @@ -274,7 +274,7 @@ Next, let’s try an example that shows that the lifetime of the reference in `result` must be the smaller lifetime of the two arguments. We’ll move the declaration of the `result` variable outside the inner scope but leave the assignment of the value to the `result` variable inside the scope with -`string2`. Then we’ll move the `println!` that uses `result` to outside the +`string2`. Then, we’ll move the `println!` that uses `result` to outside the inner scope, after the inner scope has ended. The code in Listing 10-23 will not compile. @@ -309,7 +309,7 @@ disallows the code in Listing 10-23 as possibly having an invalid reference. Try designing more experiments that vary the values and lifetimes of the references passed in to the `longest` function and how the returned reference is used. Make hypotheses about whether or not your experiments will pass the -borrow checker before you compile; then check to see if you’re right! +borrow checker before you compile; then, check to see if you’re right! @@ -365,7 +365,7 @@ of the `longest` function. We’re also trying to return a reference to `result` from the function. There is no way we can specify lifetime parameters that would change the dangling reference, and Rust won’t let us create a dangling reference. In this case, the best fix would be to return an owned data type -rather than a reference so the calling function is then responsible for +rather than a reference so that the calling function is then responsible for cleaning up the value. Ultimately, lifetime syntax is about connecting the lifetimes of various @@ -380,9 +380,9 @@ would create dangling pointers or otherwise violate memory safety. ### In Struct Definitions So far, the structs we’ve defined all hold owned types. We can define structs -to hold references, but in that case we would need to add a lifetime annotation -on every reference in the struct’s definition. Listing 10-24 has a struct named -`ImportantExcerpt` that holds a string slice. +to hold references, but in that case, we would need to add a lifetime +annotation on every reference in the struct’s definition. Listing 10-24 has a +struct named `ImportantExcerpt` that holds a string slice. @@ -394,8 +394,8 @@ on every reference in the struct’s definition. Listing 10-24 has a struct name This struct has the single field `part` that holds a string slice, which is a reference. As with generic data types, we declare the name of the generic -lifetime parameter inside angle brackets after the name of the struct so we can -use the lifetime parameter in the body of the struct definition. This +lifetime parameter inside angle brackets after the name of the struct so that +we can use the lifetime parameter in the body of the struct definition. This annotation means an instance of `ImportantExcerpt` can’t outlive the reference it holds in its `part` field. @@ -422,7 +422,7 @@ without lifetime annotations. The reason this function compiles without lifetime annotations is historical: -in early versions (pre-1.0) of Rust, this code wouldn’t have compiled because +In early versions (pre-1.0) of Rust, this code wouldn’t have compiled, because every reference needed an explicit lifetime. At that time, the function signature would have been written like this: @@ -434,8 +434,8 @@ After writing a lot of Rust code, the Rust team found that Rust programmers were entering the same lifetime annotations over and over in particular situations. These situations were predictable and followed a few deterministic patterns. The developers programmed these patterns into the compiler’s code so -the borrow checker could infer the lifetimes in these situations and wouldn’t -need explicit annotations. +that the borrow checker could infer the lifetimes in these situations and +wouldn’t need explicit annotations. This piece of Rust history is relevant because it’s possible that more deterministic patterns will emerge and be added to the compiler. In the future, @@ -449,8 +449,8 @@ fits these cases, you don’t need to write the lifetimes explicitly. The elision rules don’t provide full inference. If there is still ambiguity about what lifetimes the references have after Rust applies the rules, the compiler won’t guess what the lifetime of the remaining references should be. -Instead of guessing, the compiler will give you an error that you can resolve by -adding the lifetime annotations. +Instead of guessing, the compiler will give you an error that you can resolve +by adding the lifetime annotations. Lifetimes on function or method parameters are called _input lifetimes_, and lifetimes on return values are called _output lifetimes_. @@ -486,7 +486,7 @@ references: fn first_word(s: &str) -> &str { ``` -Then the compiler applies the first rule, which specifies that each parameter +Then, the compiler applies the first rule, which specifies that each parameter gets its own lifetime. We’ll call it `'a` as usual, so now the signature is this: @@ -513,19 +513,19 @@ no lifetime parameters when we started working with it in Listing 10-20: fn longest(x: &str, y: &str) -> &str { ``` -Let’s apply the first rule: each parameter gets its own lifetime. This time we +Let’s apply the first rule: Each parameter gets its own lifetime. This time we have two parameters instead of one, so we have two lifetimes: ```rust,ignore fn longest<'a, 'b>(x: &'a str, y: &'b str) -> &str { ``` -You can see that the second rule doesn’t apply because there is more than one +You can see that the second rule doesn’t apply, because there is more than one input lifetime. The third rule doesn’t apply either, because `longest` is a function rather than a method, so none of the parameters are `self`. After working through all three rules, we still haven’t figured out what the return type’s lifetime is. This is why we got an error trying to compile the code in -Listing 10-20: the compiler worked through the lifetime elision rules but still +Listing 10-20: The compiler worked through the lifetime elision rules but still couldn’t figure out all the lifetimes of the references in the signature. Because the third rule really only applies in method signatures, we’ll look at @@ -539,9 +539,9 @@ annotate lifetimes in method signatures very often. ### In Method Definitions When we implement methods on a struct with lifetimes, we use the same syntax as -that of generic type parameters, as shown in Listing 10-11. Where we declare and -use the lifetime parameters depends on whether they’re related to the struct -fields or the method parameters and return values. +that of generic type parameters, as shown in Listing 10-11. Where we declare +and use the lifetime parameters depends on whether they’re related to the +struct fields or the method parameters and return values. Lifetime names for struct fields always need to be declared after the `impl` keyword and then used after the struct’s name because those lifetimes are part @@ -553,7 +553,7 @@ addition, the lifetime elision rules often make it so that lifetime annotations aren’t necessary in method signatures. Let’s look at some examples using the struct named `ImportantExcerpt` that we defined in Listing 10-24. -First we’ll use a method named `level` whose only parameter is a reference to +First, we’ll use a method named `level` whose only parameter is a reference to `self` and whose return value is an `i32`, which is not a reference to anything: ```rust @@ -561,8 +561,8 @@ First we’ll use a method named `level` whose only parameter is a reference to ``` The lifetime parameter declaration after `impl` and its use after the type name -are required, but we’re not required to annotate the lifetime of the reference -to `self` because of the first elision rule. +are required, but because of the first elision rule, we’re not required to +annotate the lifetime of the reference to `self`. Here is an example where the third lifetime elision rule applies: @@ -590,13 +590,17 @@ always available. Therefore, the lifetime of all string literals is `'static`. You might see suggestions in error messages to use the `'static` lifetime. But before specifying `'static` as the lifetime for a reference, think about -whether the reference you have actually lives the entire lifetime of your -program or not, and whether you want it to. Most of the time, an error message +whether or not the reference you have actually lives the entire lifetime of +your program, and whether you want it to. Most of the time, an error message suggesting the `'static` lifetime results from attempting to create a dangling reference or a mismatch of the available lifetimes. In such cases, the solution is to fix those problems, not to specify the `'static` lifetime. -## Generic Type Parameters, Trait Bounds, and Lifetimes Together + + + + +## Generic Type Parameters, Trait Bounds, and Lifetimes Let’s briefly look at the syntax of specifying generic type parameters, trait bounds, and lifetimes all in one function! @@ -630,8 +634,8 @@ this chapter: Chapter 18 discusses trait objects, which are another way to use traits. There are also more complex scenarios involving lifetime annotations that you will only need in very advanced scenarios; for those, you should read the [Rust Reference][reference]. But next, you’ll learn how to write tests in -Rust so you can make sure your code is working the way it should. +Rust so that you can make sure your code is working the way it should. [references-and-borrowing]: ch04-02-references-and-borrowing.html#references-and-borrowing [string-slices-as-parameters]: ch04-03-slices.html#string-slices-as-parameters -[reference]: ../reference/index.html +[reference]: ../reference/trait-bounds.html diff --git a/src/ch11-00-testing.md b/src/ch11-00-testing.md index b227072ef3..110d3d28b1 100644 --- a/src/ch11-00-testing.md +++ b/src/ch11-00-testing.md @@ -5,11 +5,12 @@ testing can be a very effective way to show the presence of bugs, but it is hopelessly inadequate for showing their absence.” That doesn’t mean we shouldn’t try to test as much as we can! -Correctness in our programs is the extent to which our code does what we intend -it to do. Rust is designed with a high degree of concern about the correctness -of programs, but correctness is complex and not easy to prove. Rust’s type -system shoulders a huge part of this burden, but the type system cannot catch -everything. As such, Rust includes support for writing automated software tests. +_Correctness_ in our programs is the extent to which our code does what we +intend it to do. Rust is designed with a high degree of concern about the +correctness of programs, but correctness is complex and not easy to prove. +Rust’s type system shoulders a huge part of this burden, but the type system +cannot catch everything. As such, Rust includes support for writing automated +software tests. Say we write a function `add_two` that adds 2 to whatever number is passed to it. This function’s signature accepts an integer as a parameter and returns an @@ -25,7 +26,7 @@ We can write tests that assert, for example, that when we pass `3` to the we make changes to our code to make sure any existing correct behavior has not changed. -Testing is a complex skill: although we can’t cover in one chapter every detail +Testing is a complex skill: Although we can’t cover in one chapter every detail about how to write good tests, in this chapter we will discuss the mechanics of Rust’s testing facilities. We’ll talk about the annotations and macros available to you when writing your tests, the default behavior and options diff --git a/src/ch11-01-writing-tests.md b/src/ch11-01-writing-tests.md index 392bfd02ea..e071b4c329 100644 --- a/src/ch11-01-writing-tests.md +++ b/src/ch11-01-writing-tests.md @@ -1,6 +1,6 @@ ## How to Write Tests -Tests are Rust functions that verify that the non-test code is functioning in +_Tests_ are Rust functions that verify that the non-test code is functioning in the expected manner. The bodies of test functions typically perform these three actions: @@ -28,12 +28,12 @@ fails. Whenever we make a new library project with Cargo, a test module with a test function in it is automatically generated for us. This module gives you a -template for writing your tests so you don’t have to look up the exact +template for writing your tests so that you don’t have to look up the exact structure and syntax every time you start a new project. You can add as many additional test functions and as many test modules as you want! We’ll explore some aspects of how tests work by experimenting with the template -test before we actually test any code. Then we’ll write some real-world tests +test before we actually test any code. Then, we’ll write some real-world tests that call some code that we’ve written and assert that its behavior is correct. Let’s create a new library project called `adder` that will add two numbers: @@ -66,11 +66,11 @@ cd ../../.. -The file starts with an example `add` function, so that we have something -to test. +The file starts with an example `add` function so that we have something to +test. For now, let’s focus solely on the `it_works` function. Note the `#[test]` -annotation: this attribute indicates this is a test function, so the test +annotation: This attribute indicates this is a test function, so the test runner knows to treat this function as a test. We might also have non-test functions in the `tests` module to help set up common scenarios or perform common operations, so we always need to indicate which functions are tests. @@ -97,13 +97,13 @@ and that the result of running that test is `ok`. The overall summary `test result: ok.` means that all the tests passed, and the portion that reads `1 passed; 0 failed` totals the number of tests that passed or failed. -It’s possible to mark a test as ignored so it doesn’t run in a particular -instance; we’ll cover that in the [“Ignoring Some Tests Unless Specifically +It’s possible to mark a test as ignored so that it doesn’t run in a particular +instance; we’ll cover that in the [“Ignoring Tests Unless Specifically Requested”][ignoring] section later in this chapter. Because we haven’t done that here, the summary shows `0 ignored`. We can also pass an argument to the `cargo test` command to run only tests whose name matches a -string; this is called _filtering_ and we’ll cover it in the [“Running a -Subset of Tests by Name”][subset] section. Here we haven’t +string; this is called _filtering_, and we’ll cover it in the [“Running a +Subset of Tests by Name”][subset] section. Here, we haven’t filtered the tests being run, so the end of the summary shows `0 filtered out`. The `0 measured` statistic is for benchmark tests that measure performance. @@ -127,7 +127,7 @@ the `it_works` function to a different name, such as `exploration`, like so: {{#rustdoc_include ../listings/ch11-writing-automated-tests/no-listing-01-changing-test-name/src/lib.rs}} ``` -Then run `cargo test` again. The output now shows `exploration` instead of +Then, run `cargo test` again. The output now shows `exploration` instead of `it_works`: ```console @@ -166,23 +166,27 @@ check the line number of the panic matches the line number in the following para --> Instead of `ok`, the line `test tests::another` shows `FAILED`. Two new -sections appear between the individual results and the summary: the first +sections appear between the individual results and the summary: The first displays the detailed reason for each test failure. In this case, we get the details that `tests::another` failed because it panicked with the message `Make this test fail` on line 17 in the _src/lib.rs_ file. The next section lists just the names of all the failing tests, which is useful when there are lots of tests and lots of detailed failing test output. We can use the name of a -failing test to run just that test to more easily debug it; we’ll talk more +failing test to run just that test to debug it more easily; we’ll talk more about ways to run tests in the [“Controlling How Tests Are Run”][controlling-how-tests-are-run] section. -The summary line displays at the end: overall, our test result is `FAILED`. We +The summary line displays at the end: Overall, our test result is `FAILED`. We had one test pass and one test fail. Now that you’ve seen what the test results look like in different scenarios, let’s look at some macros other than `panic!` that are useful in tests. -### Checking Results with the `assert!` Macro + + + + +### Checking Results with `assert!` The `assert!` macro, provided by the standard library, is useful when you want to ensure that some condition in a test evaluates to `true`. We give the @@ -227,7 +231,7 @@ a glob here, so anything we define in the outer module is available to this `tests` module. We’ve named our test `larger_can_hold_smaller`, and we’ve created the two -`Rectangle` instances that we need. Then we called the `assert!` macro and +`Rectangle` instances that we need. Then, we called the `assert!` macro and passed it the result of calling `larger.can_hold(&smaller)`. This expression is supposed to return `true`, so our test should pass. Let’s find out! @@ -254,8 +258,8 @@ result, our test will pass if `can_hold` returns `false`: Two tests that pass! Now let’s see what happens to our test results when we introduce a bug in our code. We’ll change the implementation of the `can_hold` -method by replacing the greater-than sign with a less-than sign when it -compares the widths: +method by replacing the greater-than sign (`>`) with a less-than sign (`<`) +when it compares the widths: ```rust,not_desired_behavior,noplayground {{#rustdoc_include ../listings/ch11-writing-automated-tests/no-listing-03-introducing-a-bug/src/lib.rs:here}} @@ -271,7 +275,11 @@ Our tests caught the bug! Because `larger.width` is `8` and `smaller.width` is `5`, the comparison of the widths in `can_hold` now returns `false`: 8 is not less than 5. -### Testing Equality with the `assert_eq!` and `assert_ne!` Macros + + + + +### Testing Equality with `assert_eq!` and `assert_ne!` A common way to verify functionality is to test for equality between the result of the code under test and the value you expect the code to return. You could @@ -285,7 +293,7 @@ fails, which makes it easier to see _why_ the test failed; conversely, the expression, without printing the values that led to the `false` value. In Listing 11-7, we write a function named `add_two` that adds `2` to its -parameter, then we test this function using the `assert_eq!` macro. +parameter, and then we test this function using the `assert_eq!` macro. @@ -302,7 +310,7 @@ Let’s check that it passes! ``` We create a variable named `result` that holds the result of calling -`add_two(2)`. Then we pass `result` and `4` as the arguments to the +`add_two(2)`. Then, we pass `result` and `4` as the arguments to the `assert_eq!` macro. The output line for this test is `test tests::it_adds_two ... ok`, and the `ok` text indicates that our test passed! @@ -321,8 +329,8 @@ Run the tests again: Our test caught the bug! The `tests::it_adds_two` test failed, and the message tells us that the assertion that failed was `left == right` and what the `left` -and `right` values are. This message helps us start debugging: the `left` -argument, where we had the result of calling `add_two(2)`, was `5` but the +and `right` values are. This message helps us start debugging: The `left` +argument, where we had the result of calling `add_two(2)`, was `5`, but the `right` argument was `4`. You can imagine that this would be especially helpful when we have a lot of tests going on. @@ -332,14 +340,14 @@ we specify the arguments matters. However, in Rust, they’re called `left` and `right`, and the order in which we specify the value we expect and the value the code produces doesn’t matter. We could write the assertion in this test as `assert_eq!(4, result)`, which would result in the same failure message that -displays `` assertion `left == right` failed``. +displays `` assertion `left == right` failed ``. The `assert_ne!` macro will pass if the two values we give it are not equal and -fail if they’re equal. This macro is most useful for cases when we’re not sure -what a value _will_ be, but we know what the value definitely _shouldn’t_ be. -For example, if we’re testing a function that is guaranteed to change its input -in some way, but the way in which the input is changed depends on the day of -the week that we run our tests, the best thing to assert might be that the +will fail if they are equal. This macro is most useful for cases when we’re not +sure what a value _will_ be, but we know what the value definitely _shouldn’t_ +be. For example, if we’re testing a function that is guaranteed to change its +input in some way, but the way in which the input is changed depends on the day +of the week that we run our tests, the best thing to assert might be that the output of the function is not equal to the input. Under the surface, the `assert_eq!` and `assert_ne!` macros use the operators @@ -360,8 +368,8 @@ details about these and other derivable traits. You can also add a custom message to be printed with the failure message as optional arguments to the `assert!`, `assert_eq!`, and `assert_ne!` macros. Any arguments specified after the required arguments are passed along to the -`format!` macro (discussed in [“Concatenation with the `+` Operator or the -`format!` Macro”][concatenation-with-the--operator-or-the-format-macro] in Chapter 8), so you can pass a format string that contains `{}` placeholders and values to go in those placeholders. Custom messages are useful for documenting what an assertion means; when a test fails, you’ll have a better @@ -507,12 +515,12 @@ This time when we run the `should_panic` test, it will fail: The failure message indicates that this test did indeed panic as we expected, but the panic message did not include the expected string `less than or equal to 100`. The panic message that we did get in this case was `Guess value must -be greater than or equal to 1, got 200.` Now we can start figuring out where +be greater than or equal to 1, got 200`. Now we can start figuring out where our bug is! ### Using `Result` in Tests -Our tests so far all panic when they fail. We can also write tests that use +All of our tests so far panic when they fail. We can also write tests that use `Result`! Here’s the test from Listing 11-1, rewritten to use `Result` and return an `Err` instead of panicking: @@ -525,9 +533,10 @@ body of the function, rather than calling the `assert_eq!` macro, we return `Ok(())` when the test passes and an `Err` with a `String` inside when the test fails. -Writing tests so they return a `Result` enables you to use the question -mark operator in the body of tests, which can be a convenient way to write -tests that should fail if any operation within them returns an `Err` variant. +Writing tests so that they return a `Result` enables you to use the +question mark operator in the body of tests, which can be a convenient way to +write tests that should fail if any operation within them returns an `Err` +variant. You can’t use the `#[should_panic]` annotation on tests that use `Result`. To assert that an operation returns an `Err` variant, _don’t_ use the @@ -538,9 +547,9 @@ Now that you know several ways to write tests, let’s look at what is happening when we run our tests and explore the different options we can use with `cargo test`. -[concatenation-with-the--operator-or-the-format-macro]: ch08-02-strings.html#concatenation-with-the--operator-or-the-format-macro +[concatenating]: ch08-02-strings.html#concatenating-with--or-format [bench]: ../unstable-book/library-features/test.html -[ignoring]: ch11-02-running-tests.html#ignoring-some-tests-unless-specifically-requested +[ignoring]: ch11-02-running-tests.html#ignoring-tests-unless-specifically-requested [subset]: ch11-02-running-tests.html#running-a-subset-of-tests-by-name [controlling-how-tests-are-run]: ch11-02-running-tests.html#controlling-how-tests-are-run [derivable-traits]: appendix-03-derivable-traits.html diff --git a/src/ch11-02-running-tests.md b/src/ch11-02-running-tests.md index 230e6a7c82..788ff64596 100644 --- a/src/ch11-02-running-tests.md +++ b/src/ch11-02-running-tests.md @@ -13,29 +13,28 @@ binary. To separate these two types of arguments, you list the arguments that go to `cargo test` followed by the separator `--` and then the ones that go to the test binary. Running `cargo test --help` displays the options you can use with `cargo test`, and running `cargo test -- --help` displays the options you -can use after the separator. Those options are also documented in [the “Tests” -section][tests] of the [the rustc book][rustc]. +can use after the separator. These options are also documented in [the “Tests” +section of _The `rustc` Book_][tests]. [tests]: https://doc.rust-lang.org/rustc/tests/index.html -[rustc]: https://doc.rust-lang.org/rustc/index.html ### Running Tests in Parallel or Consecutively When you run multiple tests, by default they run in parallel using threads, -meaning they finish running faster and you get feedback quicker. Because the -tests are running at the same time, you must make sure your tests don’t depend -on each other or on any shared state, including a shared environment, such as -the current working directory or environment variables. +meaning they finish running more quickly and you get feedback sooner. Because +the tests are running at the same time, you must make sure your tests don’t +depend on each other or on any shared state, including a shared environment, +such as the current working directory or environment variables. For example, say each of your tests runs some code that creates a file on disk -named _test-output.txt_ and writes some data to that file. Then each test reads -the data in that file and asserts that the file contains a particular value, -which is different in each test. Because the tests run at the same time, one -test might overwrite the file in the time between another test writing and -reading the file. The second test will then fail, not because the code is -incorrect but because the tests have interfered with each other while running -in parallel. One solution is to make sure each test writes to a different file; -another solution is to run the tests one at a time. +named _test-output.txt_ and writes some data to that file. Then, each test +reads the data in that file and asserts that the file contains a particular +value, which is different in each test. Because the tests run at the same time, +one test might overwrite the file in the time between when another test is +writing and reading the file. The second test will then fail, not because the +code is incorrect but because the tests have interfered with each other while +running in parallel. One solution is to make sure each test writes to a +different file; another solution is to run the tests one at a time. If you don’t want to run the tests in parallel or if you want more fine-grained control over the number of threads used, you can send the `--test-threads` flag @@ -97,7 +96,7 @@ see the following output: ### Running a Subset of Tests by Name -Sometimes, running a full test suite can take a long time. If you’re working on +Running a full test suite can sometimes take a long time. If you’re working on code in a particular area, you might want to run only the tests pertaining to that code. You can choose which tests to run by passing `cargo test` the name or names of the test(s) you want to run as an argument. diff --git a/src/ch11-03-test-organization.md b/src/ch11-03-test-organization.md index 6f71b9a750..dad604f6d0 100644 --- a/src/ch11-03-test-organization.md +++ b/src/ch11-03-test-organization.md @@ -73,10 +73,10 @@ Note that the `internal_adder` function is not marked as `pub`. Tests are just Rust code, and the `tests` module is just another module. As we discussed in [“Paths for Referring to an Item in the Module Tree”][paths], items in child modules can use the items in their ancestor modules. In this -test, we bring all of the `tests` module’s parent’s items into scope with `use -super::*`, and then the test can call `internal_adder`. If you don’t think -private functions should be tested, there’s nothing in Rust that will compel you -to do so. +test, we bring all of the items belonging to the `tests` module’s parent into +scope with `use super::*`, and then the test can call `internal_adder`. If you +don’t think private functions should be tested, there’s nothing in Rust that +will compel you to do so. ### Integration Tests @@ -120,7 +120,7 @@ Enter the code in Listing 11-13 into the _tests/integration_test.rs_ file. Each file in the _tests_ directory is a separate crate, so we need to bring our -library into each test crate’s scope. For that reason we add `use +library into each test crate’s scope. For that reason, we add `use adder::add_two;` at the top of the code, which we didn’t need in the unit tests. We don’t need to annotate any code in _tests/integration_test.rs_ with @@ -134,7 +134,7 @@ in this directory only when we run `cargo test`. Run `cargo test` now: The three sections of output include the unit tests, the integration test, and the doc tests. Note that if any test in a section fails, the following sections will not be run. For example, if a unit test fails, there won’t be any output -for integration and doc tests because those tests will only be run if all unit +for integration and doc tests, because those tests will only be run if all unit tests are passing. The first section for the unit tests is the same as we’ve been seeing: one line @@ -172,7 +172,7 @@ share the same behavior as files in _src_ do, as you learned in Chapter 7 regarding how to separate code into modules and files. The different behavior of _tests_ directory files is most noticeable when you -have a set of helper functions to use in multiple integration test files and +have a set of helper functions to use in multiple integration test files, and you try to follow the steps in the [“Separating Modules into Different Files”][separating-modules-into-files] section of Chapter 7 to extract them into a common module. For example, if we create _tests/common.rs_ @@ -250,8 +250,8 @@ file will work as well, and that small amount of code doesn’t need to be teste ## Summary Rust’s testing features provide a way to specify how code should function to -ensure it continues to work as you expect, even as you make changes. Unit tests -exercise different parts of a library separately and can test private +ensure that it continues to work as you expect, even as you make changes. Unit +tests exercise different parts of a library separately and can test private implementation details. Integration tests check that many parts of the library work together correctly, and they use the library’s public API to test the code in the same way external code will use it. Even though Rust’s type system and diff --git a/src/ch12-00-an-io-project.md b/src/ch12-00-an-io-project.md index e421cc12f0..67650f12de 100644 --- a/src/ch12-00-an-io-project.md +++ b/src/ch12-00-an-io-project.md @@ -10,7 +10,7 @@ an ideal language for creating command line tools, so for our project, we’ll make our own version of the classic command line search tool `grep` (**g**lobally search a **r**egular **e**xpression and **p**rint). In the simplest use case, `grep` searches a specified file for a specified string. To -do so, `grep` takes as its arguments a file path and a string. Then it reads +do so, `grep` takes as its arguments a file path and a string. Then, it reads the file, finds lines in that file that contain the string argument, and prints those lines. diff --git a/src/ch12-01-accepting-command-line-arguments.md b/src/ch12-01-accepting-command-line-arguments.md index 34e4504249..46f2f37f00 100644 --- a/src/ch12-01-accepting-command-line-arguments.md +++ b/src/ch12-01-accepting-command-line-arguments.md @@ -2,7 +2,7 @@ Let’s create a new project with, as always, `cargo new`. We’ll call our project `minigrep` to distinguish it from the `grep` tool that you might already have -on your system. +on your system: ```console $ cargo new minigrep @@ -31,13 +31,13 @@ To enable `minigrep` to read the values of command line arguments we pass to it, we’ll need the `std::env::args` function provided in Rust’s standard library. This function returns an iterator of the command line arguments passed to `minigrep`. We’ll cover iterators fully in [Chapter 13][ch13]. For now, you only need to know two details about iterators: iterators +-->. For now, you only need to know two details about iterators: Iterators produce a series of values, and we can call the `collect` method on an iterator -to turn it into a collection, such as a vector, that contains all the elements +to turn it into a collection, such as a vector, which contains all the elements the iterator produces. The code in Listing 12-1 allows your `minigrep` program to read any command -line arguments passed to it, and then collect the values into a vector. +line arguments passed to it and then collect the values into a vector. @@ -47,8 +47,8 @@ line arguments passed to it, and then collect the values into a vector. -First we bring the `std::env` module into scope with a `use` statement so we -can use its `args` function. Notice that the `std::env::args` function is +First, we bring the `std::env` module into scope with a `use` statement so that +we can use its `args` function. Notice that the `std::env::args` function is nested in two levels of modules. As we discussed in [Chapter 7][ch7-idiomatic-use], in cases where the desired function is nested in more than one module, we’ve chosen to bring the parent module into @@ -97,8 +97,8 @@ chapter, we’ll ignore it and save only the two arguments we need. The program is currently able to access the values specified as command line arguments. Now we need to save the values of the two arguments in variables so -we can use the values throughout the rest of the program. We do that in Listing -12-2. +that we can use the values throughout the rest of the program. We do that in +Listing 12-2. diff --git a/src/ch12-02-reading-a-file.md b/src/ch12-02-reading-a-file.md index bc6d79139b..ce2eae5cc6 100644 --- a/src/ch12-02-reading-a-file.md +++ b/src/ch12-02-reading-a-file.md @@ -1,7 +1,7 @@ ## Reading a File Now we’ll add functionality to read the file specified in the `file_path` -argument. First we need a sample file to test it with: we’ll use a file with a +argument. First, we need a sample file to test it with: We’ll use a file with a small amount of text over multiple lines with some repeated words. Listing 12-3 has an Emily Dickinson poem that will work well! Create a file called _poem.txt_ at the root level of your project, and enter the poem “I’m Nobody! @@ -26,15 +26,15 @@ shown in Listing 12-4. -First we bring in a relevant part of the standard library with a `use` -statement: we need `std::fs` to handle files. +First, we bring in a relevant part of the standard library with a `use` +statement: We need `std::fs` to handle files. In `main`, the new statement `fs::read_to_string` takes the `file_path`, opens that file, and returns a value of type `std::io::Result` that contains the file’s contents. After that, we again add a temporary `println!` statement that prints the value -of `contents` after the file is read, so we can check that the program is +of `contents` after the file is read so that we can check that the program is working so far. Let’s run this code with any string as the first command line argument (because @@ -47,7 +47,7 @@ second argument: Great! The code read and then printed the contents of the file. But the code has a few flaws. At the moment, the `main` function has multiple -responsibilities: generally, functions are clearer and easier to maintain if +responsibilities: Generally, functions are clearer and easier to maintain if each function is responsible for only one idea. The other problem is that we’re not handling errors as well as we could. The program is still small, so these flaws aren’t a big problem, but as the program grows, it will be harder to fix diff --git a/src/ch12-03-improving-error-handling-and-modularity.md b/src/ch12-03-improving-error-handling-and-modularity.md index 53e16343b9..5d3db28dac 100644 --- a/src/ch12-03-improving-error-handling-and-modularity.md +++ b/src/ch12-03-improving-error-handling-and-modularity.md @@ -2,14 +2,14 @@ To improve our program, we’ll fix four problems that have to do with the program’s structure and how it’s handling potential errors. First, our `main` -function now performs two tasks: it parses arguments and reads files. As our +function now performs two tasks: It parses arguments and reads files. As our program grows, the number of separate tasks the `main` function handles will increase. As a function gains responsibilities, it becomes more difficult to reason about, harder to test, and harder to change without breaking one of its -parts. It’s best to separate functionality so each function is responsible for -one task. +parts. It’s best to separate functionality so that each function is responsible +for one task. -This issue also ties into the second problem: although `query` and `file_path` +This issue also ties into the second problem: Although `query` and `file_path` are configuration variables to our program, variables like `contents` are used to perform the program’s logic. The longer `main` becomes, the more variables we’ll need to bring into scope; the more variables we have in scope, the harder @@ -18,7 +18,7 @@ configuration variables into one structure to make their purpose clear. The third problem is that we’ve used `expect` to print an error message when reading the file fails, but the error message just prints `Should have been -able to read the file`. Reading a file can fail in a number of ways: for +able to read the file`. Reading a file can fail in a number of ways: For example, the file could be missing, or we might not have permission to open it. Right now, regardless of the situation, we’d print the same error message for everything, which wouldn’t give the user any information! @@ -26,10 +26,10 @@ everything, which wouldn’t give the user any information! Fourth, we use `expect` to handle an error, and if the user runs our program without specifying enough arguments, they’ll get an `index out of bounds` error from Rust that doesn’t clearly explain the problem. It would be best if all the -error-handling code were in one place so future maintainers had only one place -to consult the code if the error-handling logic needed to change. Having all the -error-handling code in one place will also ensure that we’re printing messages -that will be meaningful to our end users. +error-handling code were in one place so that future maintainers had only one +place to consult the code if the error-handling logic needed to change. Having +all the error-handling code in one place will also ensure that we’re printing +messages that will be meaningful to our end users. Let’s address these four problems by refactoring our project. @@ -152,10 +152,10 @@ giving up a little performance to gain simplicity is a worthwhile trade-off. > easier to start with the most efficient solution, but for now, it’s > perfectly acceptable to call `clone`. -We’ve updated `main` so it places the instance of `Config` returned by +We’ve updated `main` so that it places the instance of `Config` returned by `parse_config` into a variable named `config`, and we updated the code that -previously used the separate `query` and `file_path` variables so it now uses -the fields on the `Config` struct instead. +previously used the separate `query` and `file_path` variables so that it now +uses the fields on the `Config` struct instead. Now our code more clearly conveys that `query` and `file_path` are related and that their purpose is to configure how the program will work. Any code that @@ -171,7 +171,7 @@ relationship should be conveyed in our code. We then added a `Config` struct to name the related purpose of `query` and `file_path` and to be able to return the values’ names as struct field names from the `parse_config` function. -So now that the purpose of the `parse_config` function is to create a `Config` +So, now that the purpose of the `parse_config` function is to create a `Config` instance, we can change `parse_config` from a plain function to a function named `new` that is associated with the `Config` struct. Making this change will make the code more idiomatic. We can create instances of types in the @@ -237,9 +237,9 @@ arguments again to see what the error looks like now: {{#include ../listings/ch12-an-io-project/listing-12-08/output.txt}} ``` -This output is better: we now have a reasonable error message. However, we also +This output is better: We now have a reasonable error message. However, we also have extraneous information we don’t want to give to our users. Perhaps the -technique we used in Listing 9-13 isn’t the best one to use here: a call to +technique we used in Listing 9-13 isn’t the best one to use here: A call to `panic!` is more appropriate for a programming problem than a usage problem, [as discussed in Chapter 9][ch9-error-guidelines]. Instead, we’ll use the other technique you learned about in Chapter 9—[returning a @@ -256,7 +256,7 @@ the successful case and will describe the problem in the error case. We’re als going to change the function name from `new` to `build` because many programmers expect `new` functions to never fail. When `Config::build` is communicating to `main`, we can use the `Result` type to signal there was a -problem. Then we can change `main` to convert an `Err` variant into a more +problem. Then, we can change `main` to convert an `Err` variant into a more practical error for our users without the surrounding text about `thread 'main'` and `RUST_BACKTRACE` that a call to `panic!` causes. @@ -277,7 +277,7 @@ Our `build` function returns a `Result` with a `Config` instance in the success case and a string literal in the error case. Our error values will always be string literals that have the `'static` lifetime. -We’ve made two changes in the body of the function: instead of calling `panic!` +We’ve made two changes in the body of the function: Instead of calling `panic!` when the user doesn’t pass enough arguments, we now return an `Err` value, and we’ve wrapped the `Config` return value in an `Ok`. These changes make the function conform to its new type signature. @@ -311,8 +311,8 @@ In this listing, we’ve used a method we haven’t covered in detail yet: `unwrap_or_else`, which is defined on `Result` by the standard library. Using `unwrap_or_else` allows us to define some custom, non-`panic!` error handling. If the `Result` is an `Ok` value, this method’s behavior is similar -to `unwrap`: it returns the inner value that `Ok` is wrapping. However, if the -value is an `Err` value, this method calls the code in the _closure_, which is +to `unwrap`: It returns the inner value that `Ok` is wrapping. However, if the +value is an `Err` value, this method calls the code in the closure, which is an anonymous function we define and pass as an argument to `unwrap_or_else`. We’ll cover closures in more detail in [Chapter 13][ch13]. For now, you just need to know that `unwrap_or_else` will pass the inner value of @@ -323,7 +323,7 @@ appears between the vertical pipes. The code in the closure can then use the We’ve added a new `use` line to bring `process` from the standard library into scope. The code in the closure that will be run in the error case is only two -lines: we print the `err` value and then call `process::exit`. The +lines: We print the `err` value and then call `process::exit`. The `process::exit` function will stop the program immediately and return the number that was passed as the exit status code. This is similar to the `panic!`-based handling we used in Listing 12-8, but we no longer get all the @@ -342,7 +342,7 @@ Great! This output is much friendlier for our users. ### Extracting Logic from `main` Now that we’ve finished refactoring the configuration parsing, let’s turn to -the program’s logic. As we stated in [“Separation of Concerns for Binary +the program’s logic. As we stated in [“Separating Concerns in Binary Projects”](#separation-of-concerns-for-binary-projects), we’ll extract a function named `run` that will hold all the logic currently in the `main` function that isn’t involved with setting up configuration or handling @@ -364,7 +364,11 @@ The `run` function now contains all the remaining logic from `main`, starting from reading the file. The `run` function takes the `Config` instance as an argument. -#### Returning Errors from the `run` Function + + + + +#### Returning Errors from `run` With the remaining program logic separated into the `run` function, we can improve the error handling, as we did with `Config::build` in Listing 12-9. @@ -387,14 +391,14 @@ the `run` function to `Result<(), Box>`. This function previously returned the unit type, `()`, and we keep that as the value returned in the `Ok` case. -For the error type, we used the _trait object_ `Box` (and we’ve -brought `std::error::Error` into scope with a `use` statement at the top). -We’ll cover trait objects in [Chapter 18][ch18]. For now, just -know that `Box` means the function will return a type that -implements the `Error` trait, but we don’t have to specify what particular type -the return value will be. This gives us flexibility to return error values that -may be of different types in different error cases. The `dyn` keyword is short -for _dynamic_. +For the error type, we used the trait object `Box` (and we brought +`std::error::Error` into scope with a `use` statement at the top). We’ll cover +trait objects in [Chapter 18][ch18]. For now, just know that +`Box` means the function will return a type that implements the +`Error` trait, but we don’t have to specify what particular type the return +value will be. This gives us flexibility to return error values that may be of +different types in different error cases. The `dyn` keyword is short for +_dynamic_. Second, we’ve removed the call to `expect` in favor of the `?` operator, as we talked about in [Chapter 9][ch9-question-mark]. Rather than @@ -404,7 +408,7 @@ for the caller to handle. Third, the `run` function now returns an `Ok` value in the success case. We’ve declared the `run` function’s success type as `()` in the signature, which means we need to wrap the unit type value in the `Ok` value. This -`Ok(())` syntax might look a bit strange at first, but using `()` like this is +`Ok(())` syntax might look a bit strange at first. But using `()` like this is the idiomatic way to indicate that we’re calling `run` for its side effects only; it doesn’t return a value we need. @@ -438,7 +442,7 @@ the success case, we only care about detecting an error, so we don’t need `unwrap_or_else` to return the unwrapped value, which would only be `()`. The bodies of the `if let` and the `unwrap_or_else` functions are the same in -both cases: we print the error and exit. +both cases: We print the error and exit. ### Splitting Code into a Library Crate @@ -455,7 +459,7 @@ First, let’s define the `search` function signature in _src/lib.rs_ as shown i Listing 12-13, with a body that calls the `unimplemented!` macro. We’ll explain the signature in more detail when we fill in the implementation. -+ ```rust,ignore,does_not_compile {{#rustdoc_include ../listings/ch12-an-io-project/listing-12-13/src/lib.rs}} @@ -481,7 +485,7 @@ binary crate in _src/main.rs_ and call it, as shown in Listing 12-14. We add a `use minigrep::search` line to bring the `search` function from the library crate into the binary crate’s scope. Then, in the `run` function, rather than printing out the contents of the file, we call the `search` -function and pass the `config.query` value and `contents` as arguments. Then +function and pass the `config.query` value and `contents` as arguments. Then, `run` will use a `for` loop to print each line returned from `search` that matched the query. This is also a good time to remove the `println!` calls in the `main` function that displayed the query and the file path so that our @@ -489,7 +493,7 @@ program only prints the search results (if no errors occur). Note that the search function will be collecting all the results into a vector it returns before any printing happens. This implementation could be slow to -display results when searching large files because results aren’t printed as +display results when searching large files, because results aren’t printed as they’re found; we’ll discuss a possible way to fix this using iterators in Chapter 13. @@ -498,7 +502,7 @@ future. Now it’s much easier to handle errors, and we’ve made the code more modular. Almost all of our work will be done in _src/lib.rs_ from here on out. Let’s take advantage of this newfound modularity by doing something that would -have been difficult with the old code but is easy with the new code: we’ll +have been difficult with the old code but is easy with the new code: We’ll write some tests! [ch13]: ch13-00-functional-features.html diff --git a/src/ch12-04-testing-the-librarys-functionality.md b/src/ch12-04-testing-the-librarys-functionality.md index f179639382..b5b5e4d997 100644 --- a/src/ch12-04-testing-the-librarys-functionality.md +++ b/src/ch12-04-testing-the-librarys-functionality.md @@ -20,7 +20,7 @@ the test-driven development (TDD) process with the following steps: Though it’s just one of many ways to write software, TDD can help drive code design. Writing the test before you write the code that makes the test pass -helps to maintain high test coverage throughout the process. +helps maintain high test coverage throughout the process. We’ll test-drive the implementation of the functionality that will actually do the searching for the query string in the file contents and produce a list of @@ -31,7 +31,7 @@ lines that match the query. We’ll add this functionality in a function called In _src/lib.rs_, we’ll add a `tests` module with a test function, as we did in [Chapter 11][ch11-anatomy]. The test function specifies the -behavior we want the `search` function to have: it will take a query and the +behavior we want the `search` function to have: It will take a query and the text to search, and it will return only the lines from the text that contain the query. Listing 12-15 shows this test. @@ -53,11 +53,11 @@ If we run this test, it will currently fail because the `unimplemented!` macro panics with the message “not implemented”. In accordance with TDD principles, we’ll take a small step of adding just enough code to get the test to not panic when calling the function by defining the `search` function to always return an -empty vector, as shown in Listing 12-16. Then the test should compile and fail +empty vector, as shown in Listing 12-16. Then, the test should compile and fail because an empty vector doesn’t match a vector containing the line `"safe, -fast, productive."` +fast, productive."`. -+ ```rust,noplayground {{#rustdoc_include ../listings/ch12-an-io-project/listing-12-16/src/lib.rs:here}} @@ -129,7 +129,7 @@ this won’t compile yet. The `lines` method returns an iterator. We’ll talk about iterators in depth in -[Chapter 13][ch13-iterators], but recall that you saw this way +[Chapter 13][ch13-iterators]. But recall that you saw this way of using an iterator in [Listing 3-5][ch3-iter], where we used a `for` loop with an iterator to run some code on each item in a collection. @@ -159,7 +159,7 @@ to return. For that, we can make a mutable vector before the `for` loop and call the `push` method to store a `line` in the vector. After the `for` loop, we return the vector, as shown in Listing 12-19. -+ ```rust,ignore {{#rustdoc_include ../listings/ch12-an-io-project/listing-12-19/src/lib.rs:here}} diff --git a/src/ch12-05-working-with-environment-variables.md b/src/ch12-05-working-with-environment-variables.md index d96ae6d31a..39c3dade8a 100644 --- a/src/ch12-05-working-with-environment-variables.md +++ b/src/ch12-05-working-with-environment-variables.md @@ -58,10 +58,10 @@ they’ll be the same case when we check whether the line contains the query. -First we lowercase the `query` string and store it in a new variable with the +First, we lowercase the `query` string and store it in a new variable with the same name, shadowing the original `query`. Calling `to_lowercase` on the query is necessary so that no matter whether the user’s query is `"rust"`, `"RUST"`, -`"Rust"`, or `"``rUsT``"`, we’ll treat the query as if it were `"rust"` and be +`"Rust"`, or `"rUsT"`, we’ll treat the query as if it were `"rust"` and be insensitive to the case. While `to_lowercase` will handle basic Unicode, it won’t be 100 percent accurate. If we were writing a real application, we’d want to do a bit more work here, but this section is about environment variables, @@ -69,7 +69,7 @@ not Unicode, so we’ll leave it at that here. Note that `query` is now a `String` rather than a string slice because calling `to_lowercase` creates new data rather than referencing existing data. Say the -query is `"rUsT"`, as an example: that string slice doesn’t contain a lowercase +query is `"rUsT"`, as an example: That string slice doesn’t contain a lowercase `u` or `t` for us to use, so we have to allocate a new `String` containing `"rust"`. When we pass `query` as an argument to the `contains` method now, we need to add an ampersand because the signature of `contains` is defined to take @@ -85,8 +85,8 @@ Let’s see if this implementation passes the tests: {{#include ../listings/ch12-an-io-project/listing-12-21/output.txt}} ``` -Great! They passed. Now, let’s call the new `search_case_insensitive` function -from the `run` function. First we’ll add a configuration option to the `Config` +Great! They passed. Now let’s call the new `search_case_insensitive` function +from the `run` function. First, we’ll add a configuration option to the `Config` struct to switch between case-sensitive and case-insensitive search. Adding this field will cause compiler errors because we aren’t initializing this field anywhere yet: @@ -139,11 +139,11 @@ care about the _value_ of the environment variable, just whether it’s set or unset, so we’re checking `is_ok` rather than using `unwrap`, `expect`, or any of the other methods we’ve seen on `Result`. -We pass the value in the `ignore_case` variable to the `Config` instance so the -`run` function can read that value and decide whether to call +We pass the value in the `ignore_case` variable to the `Config` instance so +that the `run` function can read that value and decide whether to call `search_case_insensitive` or `search`, as we implemented in Listing 12-22. -Let’s give it a try! First we’ll run our program without the environment +Let’s give it a try! First, we’ll run our program without the environment variable set and with the query `to`, which should match any line that contains the word _to_ in all lowercase: @@ -152,7 +152,7 @@ the word _to_ in all lowercase: ``` Looks like that still works! Now let’s run the program with `IGNORE_CASE` set -to `1` but with the same query _to_: +to `1` but with the same query `to`: ```console $ IGNORE_CASE=1 cargo run -- to poem.txt @@ -201,4 +201,4 @@ precedence if the program is run with one set to case sensitive and one set to ignore case. The `std::env` module contains many more useful features for dealing with -environment variables: check out its documentation to see what is available. +environment variables: Check out its documentation to see what is available. diff --git a/src/ch12-06-writing-to-stderr-instead-of-stdout.md b/src/ch12-06-writing-to-stderr-instead-of-stdout.md index e731f386fe..dcd55777b9 100644 --- a/src/ch12-06-writing-to-stderr-instead-of-stdout.md +++ b/src/ch12-06-writing-to-stderr-instead-of-stdout.md @@ -16,7 +16,7 @@ to use something else to print to standard error. ### Checking Where Errors Are Written -First let’s observe how the content printed by `minigrep` is currently being +First, let’s observe how the content printed by `minigrep` is currently being written to standard output, including any error messages we want to write to standard error instead. We’ll do that by redirecting the standard output stream to a file while intentionally causing an error. We won’t redirect the standard @@ -24,9 +24,10 @@ error stream, so any content sent to standard error will continue to display on the screen. Command line programs are expected to send error messages to the standard error -stream so we can still see error messages on the screen even if we redirect the -standard output stream to a file. Our program is not currently well behaved: -we’re about to see that it saves the error message output to a file instead! +stream so that we can still see error messages on the screen even if we +redirect the standard output stream to a file. Our program is not currently +well behaved: We’re about to see that it saves the error message output to a +file instead! To demonstrate this behavior, we’ll run the program with `>` and the file path, _output.txt_, that we want to redirect the standard output stream to. We won’t @@ -46,8 +47,8 @@ Problem parsing arguments: not enough arguments ``` Yup, our error message is being printed to standard output. It’s much more -useful for error messages like this to be printed to standard error so only -data from a successful run ends up in the file. We’ll change that. +useful for error messages like this to be printed to standard error so that +only data from a successful run ends up in the file. We’ll change that. ### Printing Errors to Standard Error diff --git a/src/ch13-00-functional-features.md b/src/ch13-00-functional-features.md index 3332c4d986..2ed6770a98 100644 --- a/src/ch13-00-functional-features.md +++ b/src/ch13-00-functional-features.md @@ -15,10 +15,10 @@ 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 closures and iterators to improve the I/O project in Chapter 12 -- The performance of closures and iterators (spoiler alert: they’re faster than +- The performance of closures and iterators (spoiler alert: They’re faster than you might think!) We’ve already covered some other Rust features, such as pattern matching and enums, that are also influenced by the functional style. Because mastering -closures and iterators is an important part of writing idiomatic, fast Rust +closures and iterators is an important part of writing fast, idiomatic, Rust code, we’ll devote this entire chapter to them. diff --git a/src/ch13-01-closures.md b/src/ch13-01-closures.md index bdaaab36b7..ecda1acff5 100644 --- a/src/ch13-01-closures.md +++ b/src/ch13-01-closures.md @@ -17,11 +17,12 @@ customization. + -### Capturing the Environment with Closures +### Capturing the Environment We’ll first examine how we can use closures to capture values from the -environment they’re defined in for later use. Here’s the scenario: every so +environment they’re defined in for later use. Here’s the scenario: Every so often, our T-shirt company gives away an exclusive, limited-edition shirt to someone on our mailing list as a promotion. People on the mailing list can optionally add their favorite color to their profile. If the person chosen for @@ -34,8 +35,8 @@ enum called `ShirtColor` that has the variants `Red` and `Blue` (limiting the number of colors available for simplicity). We represent the company’s inventory with an `Inventory` struct that has a field named `shirts` that contains a `Vec` representing the shirt colors currently in stock. -The method `giveaway` defined on `Inventory` gets the optional shirt -color preference of the free-shirt winner, and returns the shirt color the +The method `giveaway` defined on `Inventory` gets the optional shirt color +preference of the free-shirt winner, and it returns the shirt color the person will get. This setup is shown in Listing 13-1. @@ -85,6 +86,7 @@ code we specify to the `unwrap_or_else` method. Functions, on the other hand, are not able to capture their environment in this way. + ### Inferring and Annotating Closure Types @@ -95,8 +97,8 @@ like `fn` functions do. Type annotations are required on functions because the types are 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. Closures, on the other hand, 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. +in an exposed interface like this: They’re stored in variables, and they’re +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 @@ -222,10 +224,10 @@ This code compiles, runs, and prints: ``` 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 +the `borrows_mutably` closure: When `borrows_mutably` is defined, it captures a mutable reference to `list`. We don’t use the closure again after the closure is called, so the mutable borrow ends. Between the closure definition and the -closure call, an immutable borrow to print isn’t allowed because no other +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! @@ -260,9 +262,9 @@ main thread finishes, or the main thread might finish first. If the main thread maintained ownership of `list` but ended before the new thread and drops `list`, the immutable reference in the thread would be invalid. Therefore, the compiler requires that `list` be moved into the closure given to the new thread -so the reference will be valid. Try removing the `move` keyword or using `list` -in the main thread after the closure is defined to see what compiler errors you -get! +so that the reference will be valid. Try removing the `move` keyword or using +`list` in the main thread after the closure is defined to see what compiler +errors you get! @@ -279,7 +281,7 @@ is moved _into_ the closure), the code in the body of the closure defines what happens to the references or values when the closure is evaluated later (thus affecting what, if anything, is moved _out of_ the closure). -A closure body can do any of the following: move a captured value out of the +A closure body can do any of the following: Move a captured value out of the closure, mutate the captured value, neither move nor mutate the value, or capture nothing from the environment to begin with. @@ -293,14 +295,13 @@ depending on how the closure’s body handles the values: at least this trait because all closures can be called. A closure that moves captured values out of its body will only implement `FnOnce` and none of the other `Fn` traits because it can only be called once. -* `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. +* `FnMut` applies to closures that don’t move captured values out of their body + but might mutate the captured values. These closures can be called more than + once. * `Fn` applies to closures that don’t move captured values out of their body - and that don’t mutate captured values, as well as closures that capture - nothing from their environment. 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. + and don’t mutate captured values, as well as closures that capture nothing + from their environment. 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. Let’s look at the definition of the `unwrap_or_else` method on `Option` that we used in Listing 13-1: @@ -321,7 +322,7 @@ impl Option { 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 +`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 the additional generic type @@ -331,7 +332,7 @@ 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 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` will not call `f` more than once. 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 all three kinds of @@ -349,9 +350,9 @@ Now let’s look at the standard library method `sort_by_key`, defined on slices to see how that differs from `unwrap_or_else` and why `sort_by_key` uses `FnMut` instead of `FnOnce` for the trait bound. The closure gets one argument in the form of 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 +and it 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-7, we have a list of `Rectangle` instances and we use `sort_by_key` +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. @@ -385,13 +386,13 @@ compiler won’t let us use this closure with `sort_by_key`. -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 to count the number of times `sort_by_key` calls the closure 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` and 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 @@ -407,9 +408,9 @@ move values out of the environment. Keeping a counter in the environment and incrementing its value in the closure body is a more straightforward way to count the number of times the closure is called. 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: +`num_sort_operations` counter and can therefore be called more than once. -+ ```rust {{#rustdoc_include ../listings/ch13-functional-features/listing-13-09/src/main.rs}} diff --git a/src/ch13-02-iterators.md b/src/ch13-02-iterators.md index a0ce568e72..99f2625947 100644 --- a/src/ch13-02-iterators.md +++ b/src/ch13-02-iterators.md @@ -65,7 +65,7 @@ pub trait Iterator { ``` Notice that this definition uses some new syntax: `type Item` and `Self::Item`, -which are defining an _associated type_ with this trait. We’ll talk about +which are defining an associated type with this trait. We’ll talk about associated types in depth in Chapter 20. 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` @@ -88,11 +88,11 @@ from the vector. -Note that we needed to make `v1_iter` mutable: calling the `next` method on an +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 +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 @@ -111,7 +111,7 @@ 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 adapters_, because calling them +Methods that call `next` are called _consuming adapters_ 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 @@ -126,7 +126,7 @@ test illustrating a use of the `sum` method. -We aren’t allowed to use `v1_iter` after the call to `sum` because `sum` takes +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 @@ -156,7 +156,7 @@ However, this code produces a warning: ``` The code in Listing 13-14 doesn’t do anything; the closure we’ve specified -never gets called. The warning reminds us why: iterator adapters are lazy, and +never gets called. The warning reminds us why: Iterator adapters are lazy, and we need to consume the iterator here. To fix this warning and consume the iterator, we’ll use the `collect` method, @@ -185,6 +185,7 @@ a readable way. But because all iterators are lazy, you have to call one of the consuming adapter methods to get results from calls to iterator adapters. + ### Closures That Capture Their Environment @@ -214,15 +215,15 @@ 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`. +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. +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/ch13-03-improving-our-io-project.md b/src/ch13-03-improving-our-io-project.md index 8382e3e496..6bfeb97705 100644 --- a/src/ch13-03-improving-our-io-project.md +++ b/src/ch13-03-improving-our-io-project.md @@ -27,7 +27,8 @@ 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 `build` function doesn’t own `args`. To return ownership of a `Config` instance, we had to clone the values from the `query` -and `file_path` fields of `Config` so the `Config` instance can own its values. +and `file_path` fields of `Config` so that the `Config` instance can own its +values. With our new knowledge about iterators, we can change the `build` function to take ownership of an iterator as its argument instead of borrowing a slice. @@ -82,12 +83,12 @@ 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::build` 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` trait and -returns `String` items. +We’ve updated the signature of the `Config::build` function so that 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 [“Using Traits as Parameters”][impl-trait] +section of Chapter 10 means that `args` can be any type that implements the +`Iterator` trait 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 @@ -113,11 +114,11 @@ updates the code from Listing 12-23 to use the `next` method. 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. Then we call `next` to get the -value we want to put in the `query` field of `Config`. If `next` returns `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 `file_path` value. +`next` and do nothing with the return value. Then, we call `next` to get the +value we want to put in the `query` field of `Config`. If `next` returns +`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 `file_path` value. @@ -182,9 +183,9 @@ 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 adapters 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. +objective of the loop. This abstracts away some of the commonplace code so that +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 lower-level loop will be faster. Let’s talk about performance. diff --git a/src/ch13-04-performance.md b/src/ch13-04-performance.md index 0d00d215e1..cdc2f98fd4 100644 --- a/src/ch13-04-performance.md +++ b/src/ch13-04-performance.md @@ -26,12 +26,12 @@ 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 +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 that 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): +zero-overhead in his 2012 ETAPS keynote presentation “Foundations of C++”: > 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 diff --git a/src/ch14-00-more-about-cargo.md b/src/ch14-00-more-about-cargo.md index 4d99fedf8c..59408b8e9a 100644 --- a/src/ch14-00-more-about-cargo.md +++ b/src/ch14-00-more-about-cargo.md @@ -4,11 +4,11 @@ So far, we’ve used only the most basic features of Cargo to build, run, and test our code, but it can do a lot more. In this chapter, we’ll discuss some of its other, more advanced features to show you how to do the following: -- Customize your build through release profiles -- Publish libraries on [crates.io](https://crates.io/) -- Organize large projects with workspaces -- Install binaries from [crates.io](https://crates.io/) -- Extend Cargo using custom commands +- Customize your build through release profiles. +- Publish libraries on [crates.io](https://crates.io/). +- Organize large projects with workspaces. +- Install binaries from [crates.io](https://crates.io/). +- Extend Cargo using custom commands. Cargo can do even more than the functionality we cover in this chapter, so for a full explanation of all its features, see [its documentation](https://doc.rust-lang.org/cargo/). diff --git a/src/ch14-01-release-profiles.md b/src/ch14-01-release-profiles.md index 20c20fce6e..4bbb2d390a 100644 --- a/src/ch14-01-release-profiles.md +++ b/src/ch14-01-release-profiles.md @@ -1,6 +1,6 @@ ## Customizing Builds with Release Profiles -In Rust, _release profiles_ are predefined and customizable profiles with +In Rust, _release profiles_ are predefined, customizable profiles with different configurations that allow a programmer to have more control over various options for compiling code. Each profile is configured independently of the others. diff --git a/src/ch14-02-publishing-to-crates-io.md b/src/ch14-02-publishing-to-crates-io.md index 5c22fe05a5..2af90dc9a1 100644 --- a/src/ch14-02-publishing-to-crates-io.md +++ b/src/ch14-02-publishing-to-crates-io.md @@ -49,7 +49,7 @@ rendered, as shown in Figure 14-1. Rendered HTML documentation for the `add_one` function of `my_crate` -Figure 14-1: HTML documentation for the `add_one` +Figure 14-1: The HTML documentation for the `add_one` function #### Commonly Used Sections @@ -58,12 +58,12 @@ We used the `# Examples` Markdown heading in Listing 14-1 to create a section in the HTML with the title “Examples.” Here are some other sections that crate authors commonly use in their documentation: -- **Panics**: The scenarios in which the function being documented could - panic. Callers of the function who don’t want their programs to panic should - make sure they don’t call the function in these situations. +- **Panics**: These are the scenarios in which the function being documented + could panic. Callers of the function who don’t want their programs to panic + should make sure they don’t call the function in these situations. - **Errors**: If the function returns a `Result`, describing the kinds of errors that might occur and what conditions might cause those errors to be - returned can be helpful to callers so they can write code to handle the + returned can be helpful to callers so that they can write code to handle the different kinds of errors in different ways. - **Safety**: If the function is `unsafe` to call (we discuss unsafety in Chapter 20), there should be a section explaining why the function is unsafe @@ -76,13 +76,12 @@ interested in knowing about. #### Documentation Comments as Tests Adding example code blocks in your documentation comments can help demonstrate -how to use your library, and doing so has an additional bonus: running `cargo -test` will run the code examples in your documentation as tests! Nothing is -better than documentation with examples. But nothing is worse than examples -that don’t work because the code has changed since the documentation was -written. If we run `cargo test` with the documentation for the `add_one` -function from Listing 14-1, we will see a section in the test results that looks -like this: +how to use your library and has an additional bonus: Running `cargo test` will +run the code examples in your documentation as tests! Nothing is better than +documentation with examples. But nothing is worse than examples that don’t work +because the code has changed since the documentation was written. If we run +`cargo test` with the documentation for the `add_one` function from Listing +14-1, we will see a section in the test results that looks like this: @@ -111,16 +109,16 @@ that the example and the code are out of sync with each other! #### Contained Item Comments The style of doc comment `//!` adds documentation to the item that *contains* -the comments rather than to the items *following* the comments. We typically use -these doc comments inside the crate root file (_src/lib.rs_ by convention) or -inside a module to document the crate or the module as a whole. +the comments rather than to the items *following* the comments. We typically +use these doc comments inside the crate root file (_src/lib.rs_ by convention) +or inside a module to document the crate or the module as a whole. For example, to add documentation that describes the purpose of the `my_crate` crate that contains the `add_one` function, we add documentation comments that start with `//!` to the beginning of the _src/lib.rs_ file, as shown in Listing 14-2. -+ ```rust,ignore {{#rustdoc_include ../listings/ch14-more-about-cargo/listing-14-02/src/lib.rs:here}} @@ -134,19 +132,18 @@ that contains this comment rather than an item that follows this comment. In this case, that item is the _src/lib.rs_ file, which is the crate root. These comments describe the entire crate. -When we run `cargo doc --open`, these comments will display on the front -page of the documentation for `my_crate` above the list of public items in the +When we run `cargo doc --open`, these comments will display on the front page +of the documentation for `my_crate` above the list of public items in the crate, as shown in Figure 14-2. -Rendered HTML documentation with a comment for the crate as a whole - -Figure 14-2: Rendered documentation for `my_crate`, -including the comment describing the crate as a whole - Documentation comments within items are useful for describing crates and modules especially. Use them to explain the overall purpose of the container to help your users understand the crate’s organization. +Rendered HTML documentation with a comment for the crate as a whole + +Figure 14-2: The rendered documentation for `my_crate`, +including the comment describing the crate as a whole @@ -171,7 +168,7 @@ my_crate::UsefulType;`. The good news is that if the structure _isn’t_ convenient for others to use from another library, you don’t have to rearrange your internal organization: -instead, you can re-export items to make a public structure that’s different +Instead, you can re-export items to make a public structure that’s different from your private structure by using `pub use`. *Re-exporting* takes a public item in one location and makes it public in another location, as if it were defined in the other location instead. @@ -194,7 +191,7 @@ generated by `cargo doc` would look like. Rendered documentation for the `art` crate that lists the `kinds` and `utils` modules -Figure 14-3: Front page of the documentation for `art` +Figure 14-3: The front page of the documentation for `art` that lists the `kinds` and `utils` modules Note that the `PrimaryColor` and `SecondaryColor` types aren’t listed on the @@ -262,12 +259,12 @@ people who use the crate. Another common use of `pub use` is to re-export definitions of a dependency in the current crate to make that crate's definitions part of your crate’s public API. -Creating a useful public API structure is more of an art than a science, and -you can iterate to find the API that works best for your users. Choosing `pub -use` gives you flexibility in how you structure your crate internally and -decouples that internal structure from what you present to your users. Look at -some of the code of crates you’ve installed to see if their internal structure -differs from their public API. +Creating a useful public API structure is more an art than a science, and you +can iterate to find the API that works best for your users. Choosing `pub use` +gives you flexibility in how you structure your crate internally and decouples +that internal structure from what you present to your users. Look at some of +the code of crates you’ve installed to see if their internal structure differs +from their public API. ### Setting Up a Crates.io Account @@ -278,7 +275,7 @@ in via a GitHub account. (The GitHub account is currently a requirement, but the site might support other ways of creating an account in the future.) Once you’re logged in, visit your account settings at [https://crates.io/me/](https://crates.io/me/) and retrieve your -API key. Then run the `cargo login` command and paste your API key when prompted, like this: +API key. Then, run the `cargo login` command and paste your API key when prompted, like this: ```console $ cargo login @@ -286,7 +283,7 @@ abcdefghijklmnopqrstuvwxyz012345 ``` This command will inform Cargo of your API token and store it locally in -_~/.cargo/credentials.toml_. Note that this token is a _secret_: do not share +_~/.cargo/credentials.toml_. Note that this token is a secret: Do not share it with anyone else. If you do share it with anyone for any reason, you should revoke it and generate a new token on [crates.io](https://crates.io/). @@ -335,14 +332,14 @@ Caused by: the remote server responded with an error (status 400 Bad Request): missing or empty metadata fields: description, license. Please see https://doc.rust-lang.org/cargo/reference/manifest.html for more information on configuring these fields ``` -This results in an error because you’re missing some crucial information: a -description and license are required so people will know what your crate does -and under what terms they can use it. In _Cargo.toml_, add a description that's -just a sentence or two, because it will appear with your crate in search -results. For the `license` field, you need to give a _license identifier value_. -The [Linux Foundation’s Software Package Data Exchange (SPDX)][spdx] lists the -identifiers you can use for this value. For example, to specify that you’ve -licensed your crate using the MIT License, add the `MIT` identifier: +This results in an error because you’re missing some crucial information: A +description and license are required so that people will know what your crate +does and under what terms they can use it. In _Cargo.toml_, add a description +that's just a sentence or two, because it will appear with your crate in search +results. For the `license` field, you need to give a _license identifier +value_. The [Linux Foundation’s Software Package Data Exchange (SPDX)][spdx] +lists the identifiers you can use for this value. For example, to specify that +you’ve licensed your crate using the MIT License, add the `MIT` identifier: Filename: Cargo.toml @@ -432,7 +429,7 @@ When you’ve made changes to your crate and are ready to release a new version, you change the `version` value specified in your _Cargo.toml_ file and republish. Use the [Semantic Versioning rules][semver] to decide what an appropriate next version number is, based on the kinds of changes you’ve made. -Then run `cargo publish` to upload the new version. +Then, run `cargo publish` to upload the new version. @@ -453,9 +450,9 @@ _Cargo.lock_ files generated will not use the yanked version. To yank a version of a crate, in the directory of the crate that you’ve previously published, run `cargo yank` and specify which version you want to -yank. For example, if we've published a crate named `guessing_game` version -1.0.1 and we want to yank it, in the project directory for `guessing_game` we'd -run: +yank. For example, if we’ve published a crate named `guessing_game` version +1.0.1 and we want to yank it, then we’d run the following in the project +directory for `guessing_game`: in Chapter 18 is devoted to that topic. -So what you learn here you’ll apply again in that section! +_trait object_, and [“Using Trait Objects to Abstract over Shared +Behavior”][trait-objects] in Chapter 18 is devoted to that +topic. So, what you learn here you’ll apply again in that section! @@ -69,10 +69,10 @@ types could theoretically continue infinitely, so Rust can’t know how much spa the value needs. Because boxes have a known size, we can enable recursive types by inserting a box in the recursive type definition. -As an example of a recursive type, let’s explore the _cons list_. This is a data +As an example of a recursive type, let’s explore the cons list. This is a data type commonly found in functional programming languages. The cons list type we’ll define is straightforward except for the recursion; therefore, the -concepts in the example we’ll work with will be useful any time you get into +concepts in the example we’ll work with will be useful anytime you get into more complex situations involving recursive types. @@ -96,11 +96,11 @@ list `1, 2, 3` with each pair in parentheses: ``` Each item in a cons list contains two elements: the value of the current item -and the next item. The last item in the list contains only a value called `Nil` -without a next item. A cons list is produced by recursively calling the `cons` -function. The canonical name to denote the base case of the recursion is `Nil`. -Note that this is not the same as the “null” or “nil” concept discussed in -Chapter 6, which is an invalid or absent value. +and of the next item. The last item in the list contains only a value called +`Nil` without a next item. A cons list is produced by recursively calling the +`cons` function. The canonical name to denote the base case of the recursion is +`Nil`. Note that this is not the same as the “null” or “nil” concept discussed +in Chapter 6, which is an invalid or absent value. The cons list isn’t a commonly used data structure in Rust. Most of the time when you have a list of items in Rust, `Vec` is a better choice to use. @@ -109,7 +109,7 @@ but by starting with the cons list in this chapter, we can explore how boxes let us define a recursive data type without much distraction. Listing 15-2 contains an enum definition for a cons list. Note that this code -won’t compile yet because the `List` type doesn’t have a known size, which +won’t compile yet, because the `List` type doesn’t have a known size, which we’ll demonstrate. @@ -153,9 +153,9 @@ Listing 15-4. The error shows this type “has infinite size.” The reason is that we’ve defined -`List` with a variant that is recursive: it holds another value of itself +`List` with a variant that is recursive: It holds another value of itself directly. As a result, Rust can’t figure out how much space it needs to store a -`List` value. Let’s break down why we get this error. First we’ll look at how +`List` value. Let’s break down why we get this error. First, we’ll look at how Rust decides how much space it needs to store a value of a non-recursive type. #### Computing the Size of a Non-Recursive Type @@ -183,7 +183,7 @@ type needs, the compiler looks at the variants, starting with the `Cons` variant. The `Cons` variant holds a value of type `i32` and a value of type `List`, and this process continues infinitely, as shown in Figure 15-1. -An infinite Cons list: a rectangle labeled 'Cons' split into two smaller rectangles. The first smaller rectangle holds the label 'i32', and the second smaller rectangle holds the label 'Cons' and a smaller version of the outer 'Cons' rectangle. The 'Cons' rectangles continue to hold smaller and smaller versions of themselves until the smallest comfortably-sized rectangle holds an infinity symbol, indicating that this repetition goes on forever +An infinite Cons list: a rectangle labeled 'Cons' split into two smaller rectangles. The first smaller rectangle holds the label 'i32', and the second smaller rectangle holds the label 'Cons' and a smaller version of the outer 'Cons' rectangle. The 'Cons' rectangles continue to hold smaller and smaller versions of themselves until the smallest comfortably sized rectangle holds an infinity symbol, indicating that this repetition goes on forever. Figure 15-1: An infinite `List` consisting of infinite `Cons` variants @@ -213,7 +213,7 @@ directly, we should change the data structure to store the value indirectly by storing a pointer to the value instead. Because a `Box` is a pointer, Rust always knows how much space a `Box` -needs: a pointer’s size doesn’t change based on the amount of data it’s +needs: A pointer’s size doesn’t change based on the amount of data it’s pointing to. This means we can put a `Box` inside the `Cons` variant instead of another `List` value directly. The `Box` will point to the next `List` value that will be on the heap rather than inside the `Cons` variant. @@ -224,7 +224,7 @@ rather than inside one another. We can change the definition of the `List` enum in Listing 15-2 and the usage of the `List` in Listing 15-3 to the code in Listing 15-5, which will compile. -+ ```rust {{#rustdoc_include ../listings/ch15-smart-pointers/listing-15-05/src/main.rs}} @@ -232,17 +232,17 @@ of the `List` in Listing 15-3 to the code in Listing 15-5, which will compile. -The `Cons` variant needs the size of an `i32` plus the space to store the -box’s pointer data. The `Nil` variant stores no values, so it needs less space -on the stack than the `Cons` variant. We now know that any `List` value will -take up the size of an `i32` plus the size of a box’s pointer data. By using a -box, we’ve broken the infinite, recursive chain, so the compiler can figure out -the size it needs to store a `List` value. Figure 15-2 shows what the `Cons` +The `Cons` variant needs the size of an `i32` plus the space to store the box’s +pointer data. The `Nil` variant stores no values, so it needs less space on the +stack than the `Cons` variant. We now know that any `List` value will take up +the size of an `i32` plus the size of a box’s pointer data. By using a box, +we’ve broken the infinite, recursive chain, so the compiler can figure out the +size it needs to store a `List` value. Figure 15-2 shows what the `Cons` variant looks like now. -A rectangle labeled 'Cons' split into two smaller rectangles. The first smaller rectangle holds the label 'i32', and the second smaller rectangle holds the label 'Box' with one inner rectangle that contains the label 'usize', representing the finite size of the box's pointer +A rectangle labeled 'Cons' split into two smaller rectangles. The first smaller rectangle holds the label 'i32', and the second smaller rectangle holds the label 'Box' with one inner rectangle that contains the label 'usize', representing the finite size of the box's pointer. -Figure 15-2: A `List` that is not infinitely sized +Figure 15-2: A `List` that is not infinitely sized, because `Cons` holds a `Box` Boxes provide only the indirection and heap allocation; they don’t have any @@ -260,4 +260,4 @@ even more important to the functionality provided by the other smart pointer types we’ll discuss in the rest of this chapter. Let’s explore these two traits in more detail. -[trait-objects]: ch18-02-trait-objects.html#using-trait-objects-that-allow-for-values-of-different-types +[trait-objects]: ch18-02-trait-objects.html#using-trait-objects-to-abstract-over-shared-behavior diff --git a/src/ch15-02-deref.md b/src/ch15-02-deref.md index 5c52907e03..7a8339602a 100644 --- a/src/ch15-02-deref.md +++ b/src/ch15-02-deref.md @@ -1,10 +1,10 @@ -## Treating Smart Pointers Like Regular References - +## Treating Smart Pointers Like Regular References + Implementing the `Deref` trait allows you to customize the behavior of the _dereference operator_ `*` (not to be confused with the multiplication or glob operator). By implementing `Deref` in such a way that a smart pointer can be @@ -12,12 +12,12 @@ treated like a regular reference, you can write code that operates on references and use that code with smart pointers too. Let’s first look at how the dereference operator works with regular references. -Then we’ll try to define a custom type that behaves like `Box`, and see why +Then, we’ll try to define a custom type that behaves like `Box` and see why the dereference operator doesn’t work like a reference on our newly defined type. We’ll explore how implementing the `Deref` trait makes it possible for -smart pointers to work in ways similar to references. Then we’ll look at -Rust’s _deref coercion_ feature and how it lets us work with either references -or smart pointers. +smart pointers to work in ways similar to references. Then, we’ll look at +Rust’s deref coercion feature and how it lets us work with either references or +smart pointers. @@ -42,9 +42,9 @@ reference to the value. The variable `x` holds an `i32` value `5`. We set `y` equal to a reference to `x`. We can assert that `x` is equal to `5`. However, if we want to make an assertion about the value in `y`, we have to use `*y` to follow the reference -to the value it’s pointing to (hence _dereference_) so the compiler can compare -the actual value. Once we dereference `y`, we have access to the integer value -`y` is pointing to that we can compare with `5`. +to the value it’s pointing to (hence, _dereference_) so that the compiler can +compare the actual value. Once we dereference `y`, we have access to the +integer value `y` is pointing to that we can compare with `5`. If we tried to write `assert_eq!(5, y);` instead, we would get this compilation error: @@ -83,11 +83,11 @@ that enables us to use the dereference operator by defining our own box type. Let’s build a wrapper type similar to the `Box` type provided by the standard library to experience how smart pointer types behave differently from -references by default. Then we’ll look at how to add the ability to use the +references by default. Then, we’ll look at how to add the ability to use the dereference operator. > Note: There’s one big difference between the `MyBox` type we’re about to -> build and the real `Box`: our version will not store its data on the heap. +> build and the real `Box`: Our version will not store its data on the heap. > We are focusing this example on `Deref`, so where the data is actually stored > is less important than the pointer-like behavior. @@ -103,15 +103,15 @@ Listing 15-8 defines a `MyBox` type in the same way. We’ll also define a -We define a struct named `MyBox` and declare a generic parameter `T` because -we want our type to hold values of any type. The `MyBox` type is a tuple struct +We define a struct named `MyBox` and declare a generic parameter `T` because we +want our type to hold values of any type. The `MyBox` type is a tuple struct with one element of type `T`. The `MyBox::new` function takes one parameter of type `T` and returns a `MyBox` instance that holds the value passed in. Let’s try adding the `main` function in Listing 15-7 to Listing 15-8 and changing it to use the `MyBox` type we’ve defined instead of `Box`. The -code in Listing 15-9 won’t compile because Rust doesn’t know how to dereference -`MyBox`. +code in Listing 15-9 won’t compile, because Rust doesn’t know how to +dereference `MyBox`. @@ -152,21 +152,21 @@ of `Deref` to add to the definition of `MyBox`. -The `type Target = T;` syntax defines an associated type for the `Deref` -trait to use. Associated types are a slightly different way of declaring a -generic parameter, but you don’t need to worry about them for now; we’ll cover -them in more detail in Chapter 20. +The `type Target = T;` syntax defines an associated type for the `Deref` trait +to use. Associated types are a slightly different way of declaring a generic +parameter, but you don’t need to worry about them for now; we’ll cover them in +more detail in Chapter 20. -We fill in the body of the `deref` method with `&self.0` so `deref` returns a -reference to the value we want to access with the `*` operator; recall from -[“Using Tuple Structs Without Named Fields to Create Different -Types”][tuple-structs] in Chapter 5 that `.0` accesses the first -value in a tuple struct. The `main` function in Listing 15-9 that calls `*` on -the `MyBox` value now compiles, and the assertions pass! +We fill in the body of the `deref` method with `&self.0` so that `deref` +returns a reference to the value we want to access with the `*` operator; +recall from [“Creating Different Types with Tuple Structs”][tuple-structs] in Chapter 5 that `.0` accesses the first value in a tuple struct. +The `main` function in Listing 15-9 that calls `*` on the `MyBox` value now +compiles, and the assertions pass! Without the `Deref` trait, the compiler can only dereference `&` references. The `deref` method gives the compiler the ability to take a value of any type -that implements `Deref` and call the `deref` method to get an `&` reference that +that implements `Deref` and call the `deref` method to get a reference that it knows how to dereference. When we entered `*y` in Listing 15-9, behind the scenes Rust actually ran this @@ -177,8 +177,8 @@ code: ``` Rust substitutes the `*` operator with a call to the `deref` method and then a -plain dereference so we don’t have to think about whether or not we need to -call the `deref` method. This Rust feature lets us write code that functions +plain dereference so that we don’t have to think about whether or not we need +to call the `deref` method. This Rust feature lets us write code that functions identically whether we have a regular reference or a type that implements `Deref`. @@ -206,7 +206,7 @@ _Deref coercion_ converts a reference to a type that implements the `Deref` trait into a reference to another type. For example, deref coercion can convert `&String` to `&str` because `String` implements the `Deref` trait such that it returns `&str`. Deref coercion is a convenience Rust performs on arguments to -functions and methods, and works only on types that implement the `Deref` +functions and methods, and it works only on types that implement the `Deref` trait. It happens automatically when we pass a reference to a particular type’s value as an argument to a function or method that doesn’t match the parameter type in the function or method definition. A sequence of calls to the `deref` @@ -262,7 +262,7 @@ of type `&MyBox`. -The `(*m)` dereferences the `MyBox` into a `String`. Then the `&` and +The `(*m)` dereferences the `MyBox` into a `String`. Then, the `&` and `[..]` take a string slice of the `String` that is equal to the whole string to match the signature of `hello`. This code without deref coercions is harder to read, write, and understand with all of these symbols involved. Deref coercion @@ -293,11 +293,11 @@ cases: The first two cases are the same except that the second implements mutability. The first case states that if you have a `&T`, and `T` implements `Deref` to -some type `U`, you can get a `&U` transparently. The second case states that the -same deref coercion happens for mutable references. +some type `U`, you can get a `&U` transparently. The second case states that +the same deref coercion happens for mutable references. The third case is trickier: Rust will also coerce a mutable reference to an -immutable one. But the reverse is _not_ possible: immutable references will +immutable one. But the reverse is _not_ possible: Immutable references will never coerce to mutable references. Because of the borrowing rules, if you have a mutable reference, that mutable reference must be the only reference to that data (otherwise, the program wouldn’t compile). Converting one mutable @@ -309,4 +309,4 @@ assumption that converting an immutable reference to a mutable reference is possible. [impl-trait]: ch10-02-traits.html#implementing-a-trait-on-a-type -[tuple-structs]: ch05-01-defining-structs.html#using-tuple-structs-without-named-fields-to-create-different-types +[tuple-structs]: ch05-01-defining-structs.html#creating-different-types-with-tuple-structs diff --git a/src/ch15-03-drop.md b/src/ch15-03-drop.md index 3a82c32ca4..58b244b77b 100644 --- a/src/ch15-03-drop.md +++ b/src/ch15-03-drop.md @@ -7,15 +7,15 @@ be used to release resources like files or network connections. We’re introducing `Drop` in the context of smart pointers because the functionality of the `Drop` trait is almost always used when implementing a -smart pointer. For example, when a `Box` is dropped it will deallocate the +smart pointer. For example, when a `Box` is dropped, it will deallocate the space on the heap that the box points to. In some languages, for some types, the programmer must call code to free memory or resources every time they finish using an instance of those types. Examples -include file handles, sockets, and locks. If they forget, the system might -become overloaded and crash. In Rust, you can specify that a particular bit of -code be run whenever a value goes out of scope, and the compiler will insert -this code automatically. As a result, you don’t need to be careful about +include file handles, sockets, and locks. If the programmer forgets, the system +might become overloaded and crash. In Rust, you can specify that a particular +bit of code be run whenever a value goes out of scope, and the compiler will +insert this code automatically. As a result, you don’t need to be careful about placing cleanup code everywhere in a program that an instance of a particular type is finished with—you still won’t leak resources! @@ -70,15 +70,14 @@ Unfortunately, it’s not straightforward to disable the automatic `drop` functionality. Disabling `drop` isn’t usually necessary; the whole point of the `Drop` trait is that it’s taken care of automatically. Occasionally, however, you might want to clean up a value early. One example is when using smart -pointers that manage locks: you might want to force the `drop` method that +pointers that manage locks: You might want to force the `drop` method that releases the lock so that other code in the same scope can acquire the lock. Rust doesn’t let you call the `Drop` trait’s `drop` method manually; instead, you have to call the `std::mem::drop` function provided by the standard library if you want to force a value to be dropped before the end of its scope. -If we try to call the `Drop` trait’s `drop` method manually by modifying the -`main` function from Listing 15-14, as shown in Listing 15-15, we’ll get a -compiler error. +Trying to call the `Drop` trait’s `drop` method manually by modifying the +`main` function from Listing 15-14 won’t work, as shown in Listing 15-15. @@ -100,10 +99,9 @@ for a function that cleans up an instance. A _destructor_ is analogous to a _constructor_, which creates an instance. The `drop` function in Rust is one particular destructor. -Rust doesn’t let us call `drop` explicitly because Rust would still +Rust doesn’t let us call `drop` explicitly, because Rust would still automatically call `drop` on the value at the end of `main`. This would cause a -_double free_ error because Rust would be trying to clean up the same value -twice. +double free error because Rust would be trying to clean up the same value twice. We can’t disable the automatic insertion of `drop` when a value goes out of scope, and we can’t call the `drop` method explicitly. So, if we need to force @@ -129,17 +127,17 @@ Running this code will print the following: ``` The text ``Dropping CustomSmartPointer with data `some data`!`` is printed -between the `CustomSmartPointer created.` and `CustomSmartPointer dropped -before the end of main.` text, showing that the `drop` method code is called to -drop `c` at that point. +between the `CustomSmartPointer created` and `CustomSmartPointer dropped before +the end of main` text, showing that the `drop` method code is called to drop +`c` at that point. You can use code specified in a `Drop` trait implementation in many ways to -make cleanup convenient and safe: for instance, you could use it to create your +make cleanup convenient and safe: For instance, you could use it to create your own memory allocator! With the `Drop` trait and Rust’s ownership system, you -don’t have to remember to clean up because Rust does it automatically. +don’t have to remember to clean up, because Rust does it automatically. You also don’t have to worry about problems resulting from accidentally -cleaning up values still in use: the ownership system that makes sure +cleaning up values still in use: The ownership system that makes sure references are always valid also ensures that `drop` gets called only once when the value is no longer being used. diff --git a/src/ch15-04-rc.md b/src/ch15-04-rc.md index 2446596c78..9b5f7f86eb 100644 --- a/src/ch15-04-rc.md +++ b/src/ch15-04-rc.md @@ -1,6 +1,6 @@ -## `Rc`, the Reference Counted Smart Pointer +## `Rc`, the Reference-Counted Smart Pointer -In the majority of cases, ownership is clear: you know exactly which variable +In the majority of cases, ownership is clear: You know exactly which variable owns a given value. However, there are cases when a single value might have multiple owners. For example, in graph data structures, multiple edges might point to the same node, and that node is conceptually owned by all of the edges @@ -39,15 +39,20 @@ Let’s return to our cons list example in Listing 15-5. Recall that we defined it using `Box`. This time, we’ll create two lists that both share ownership of a third list. Conceptually, this looks similar to Figure 15-3. -A linked list with the label 'a' pointing to three elements: the first element contains the integer 5 and points to the second element. The second element contains the integer 10 and points to the third element. The third element contains the value 'Nil' that signifies the end of the list; it does not point anywhere. A linked list with the label 'b' points to an element that contains the integer 3 and points to the first element of list 'a'. A linked list with the label 'c' points to an element that contains the integer 4 and also points to the first element of list 'a', so that the tail of lists 'b' and 'c' are both list 'a' +A linked list with the label 'a' pointing to three elements. The first element contains the integer 5 and points to the second element. Th
+e second element contains the integer 10 and points to the third element. The third element contains the value 'Nil' that signifies the end of the l
+ist; it does not point anywhere. A linked list with the label 'b' points to an element that contains the integer 3 and points to the first element o
+f list 'a'. A linked list with the label 'c' points to an element that contains the integer 4 and also points to the first element of list 'a' so th
+at the tails of lists 'b' and 'c' are both list 'a'. Figure 15-3: Two lists, `b` and `c`, sharing ownership of a third list, `a` -We’ll create list `a` that contains `5` and then `10`. Then we’ll make two more -lists: `b` that starts with `3` and `c` that starts with `4`. Both `b` and `c` -lists will then continue on to the first `a` list containing `5` and `10`. In -other words, both lists will share the first list containing `5` and `10`. +We’ll create list `a` that contains `5` and then `10`. Then, we’ll make two +more lists: `b` that starts with `3` and `c` that starts with `4`. Both the `b` +and `c` lists will then continue on to the first `a` list containing `5` and +`10`. In other words, both lists will share the first list containing `5` and +`10`. Trying to implement this scenario using our definition of `List` with `Box` won’t work, as shown in Listing 15-17. @@ -118,11 +123,13 @@ code, we only need to consider the deep-copy clones and can disregard calls to ### Cloning to Increase the Reference Count -Let’s change our working example in Listing 15-18 so we can see the reference -counts changing as we create and drop references to the `Rc` in `a`. +Let’s change our working example in Listing 15-18 so that we can see the +reference counts changing as we create and drop references to the `Rc` in +`a`. -In Listing 15-19, we’ll change `main` so it has an inner scope around list `c`; -then we can see how the reference count changes when `c` goes out of scope. +In Listing 15-19, we’ll change `main` so that it has an inner scope around list +`c`; then, we can see how the reference count changes when `c` goes out of +scope. @@ -144,15 +151,15 @@ This code prints the following: {{#include ../listings/ch15-smart-pointers/listing-15-19/output.txt}} ``` -We can see that the `Rc` in `a` has an initial reference count of 1; then -each time we call `clone`, the count goes up by 1. When `c` goes out of scope, -the count goes down by 1. We don’t have to call a function to decrease the -reference count like we have to call `Rc::clone` to increase the reference -count: the implementation of the `Drop` trait decreases the reference count +We can see that the `Rc` in `a` has an initial reference count of 1; +then, each time we call `clone`, the count goes up by 1. When `c` goes out of +scope, the count goes down by 1. We don’t have to call a function to decrease +the reference count like we have to call `Rc::clone` to increase the reference +count: The implementation of the `Drop` trait decreases the reference count automatically when an `Rc` value goes out of scope. What we can’t see in this example is that when `b` and then `a` go out of scope -at the end of `main`, the count is then 0, and the `Rc` is cleaned up +at the end of `main`, the count is 0, and the `Rc` is cleaned up completely. Using `Rc` allows a single value to have multiple owners, and the count ensures that the value remains valid as long as any of the owners still exist. @@ -160,7 +167,7 @@ still exist. Via immutable references, `Rc` allows you to share data between multiple parts of your program for reading only. If `Rc` allowed you to have multiple mutable references too, you might violate one of the borrowing rules discussed -in Chapter 4: multiple mutable borrows to the same place can cause data races +in Chapter 4: Multiple mutable borrows to the same place can cause data races and inconsistencies. But being able to mutate data is very useful! In the next section, we’ll discuss the interior mutability pattern and the `RefCell` type that you can use in conjunction with an `Rc` to work with this diff --git a/src/ch15-05-interior-mutability.md b/src/ch15-05-interior-mutability.md index 91fc2bc92b..2bc04a770c 100644 --- a/src/ch15-05-interior-mutability.md +++ b/src/ch15-05-interior-mutability.md @@ -23,7 +23,7 @@ interior mutability pattern. ### Enforcing Borrowing Rules at Runtime Unlike `Rc`, the `RefCell` type represents single ownership over the data -it holds. So what makes `RefCell` different from a type like `Box`? +it holds. So, what makes `RefCell` different from a type like `Box`? Recall the borrowing rules you learned in Chapter 4: - At any given time, you can have _either_ one mutable reference or any number @@ -45,7 +45,7 @@ The advantage of checking the borrowing rules at runtime instead is that certain memory-safe scenarios are then allowed, where they would’ve been disallowed by the compile-time checks. Static analysis, like the Rust compiler, is inherently conservative. Some properties of code are impossible to detect by -analyzing the code: the most famous example is the Halting Problem, which is +analyzing the code: The most famous example is the Halting Problem, which is beyond the scope of this book but is an interesting topic to research. Because some analysis is impossible, if the Rust compiler can’t be sure the @@ -73,7 +73,7 @@ Here is a recap of the reasons to choose `Box`, `Rc`, or `RefCell`: mutate the value inside the `RefCell` even when the `RefCell` is immutable. -Mutating the value inside an immutable value is the _interior mutability_ +Mutating the value inside an immutable value is the interior mutability pattern. Let’s look at a situation in which interior mutability is useful and examine how it’s possible. @@ -100,7 +100,7 @@ However, there are situations in which it would be useful for a value to mutate itself in its methods but appear immutable to other code. Code outside the value’s methods would not be able to mutate the value. Using `RefCell` is one way to get the ability to have interior mutability, but `RefCell` -doesn’t get around the borrowing rules completely: the borrow checker in the +doesn’t get around the borrowing rules completely: The borrow checker in the compiler allows this interior mutability, and the borrowing rules are checked at runtime instead. If you violate the rules, you’ll get a `panic!` instead of a compiler error. @@ -120,7 +120,7 @@ correctly. This placeholder type is called a _test double_. Think of it in the sense of a stunt double in filmmaking, where a person steps in and substitutes for an actor to do a particularly tricky scene. Test doubles stand in for other types when we’re running tests. _Mock objects_ are specific types of test -doubles that record what happens during a test so you can assert that the +doubles that record what happens during a test so that you can assert that the correct actions took place. Rust doesn’t have objects in the same sense as other languages have objects, @@ -128,7 +128,7 @@ and Rust doesn’t have mock object functionality built into the standard librar as some other languages do. However, you can definitely create a struct that will serve the same purposes as a mock object. -Here’s the scenario we’ll test: we’ll create a library that tracks a value +Here’s the scenario we’ll test: We’ll create a library that tracks a value against a maximum value and sends messages based on how close to the maximum value the current value is. This library could be used to keep track of a user’s quota for the number of API calls they’re allowed to make, for example. @@ -136,10 +136,10 @@ user’s quota for the number of API calls they’re allowed to make, for exampl Our library will only provide the functionality of tracking how close to the maximum a value is and what the messages should be at what times. Applications that use our library will be expected to provide the mechanism for sending the -messages: the application could put a message in the application, send an email, -send a text message, or do something else. The library doesn’t need to know that -detail. All it needs is something that implements a trait we’ll provide called -`Messenger`. Listing 15-20 shows the library code. +messages: The application could show the message to the user directly, send an +email, send a text message, or do something else. The library doesn’t need to +know that detail. All it needs is something that implements a trait we’ll +provide, called `Messenger`. Listing 15-20 shows the library code. @@ -157,8 +157,8 @@ is that we want to test the behavior of the `set_value` method on the `LimitTracker`. We can change what we pass in for the `value` parameter, but `set_value` doesn’t return anything for us to make assertions on. We want to be able to say that if we create a `LimitTracker` with something that implements -the `Messenger` trait and a particular value for `max`, when we pass different -numbers for `value` the messenger is told to send the appropriate messages. +the `Messenger` trait and a particular value for `max`, the messenger is told +to send the appropriate messages when we pass different numbers for `value`. We need a mock object that, instead of sending an email or text message when we call `send`, will only keep track of the messages it’s told to send. We can @@ -179,18 +179,18 @@ This test code defines a `MockMessenger` struct that has a `sent_messages` field with a `Vec` of `String` values to keep track of the messages it’s told to send. We also define an associated function `new` to make it convenient to create new `MockMessenger` values that start with an empty list of messages. We -then implement the `Messenger` trait for `MockMessenger` so we can give a +then implement the `Messenger` trait for `MockMessenger` so that we can give a `MockMessenger` to a `LimitTracker`. In the definition of the `send` method, we take the message passed in as a parameter and store it in the `MockMessenger` list of `sent_messages`. In the test, we’re testing what happens when the `LimitTracker` is told to set -`value` to something that is more than 75 percent of the `max` value. First we +`value` to something that is more than 75 percent of the `max` value. First, we create a new `MockMessenger`, which will start with an empty list of messages. -Then we create a new `LimitTracker` and give it a reference to the new +Then, we create a new `LimitTracker` and give it a reference to the new `MockMessenger` and a `max` value of `100`. We call the `set_value` method on the `LimitTracker` with a value of `80`, which is more than 75 percent of 100. -Then we assert that the list of messages that the `MockMessenger` is keeping +Then, we assert that the list of messages that the `MockMessenger` is keeping track of should now have one message in it. However, there’s one problem with this test, as shown here: @@ -199,7 +199,7 @@ However, there’s one problem with this test, as shown here: {{#include ../listings/ch15-smart-pointers/listing-15-21/output.txt}} ``` -We can’t modify the `MockMessenger` to keep track of the messages because the +We can’t modify the `MockMessenger` to keep track of the messages, because the `send` method takes an immutable reference to `self`. We also can’t take the suggestion from the error text to use `&mut self` in both the `impl` method and the trait definition. We do not want to change the `Messenger` trait solely for @@ -207,9 +207,9 @@ the sake of testing. Instead, we need to find a way to make our test code work correctly with our existing design. This is a situation in which interior mutability can help! We’ll store the -`sent_messages` within a `RefCell`, and then the `send` method will be -able to modify `sent_messages` to store the messages we’ve seen. Listing 15-22 -shows what that looks like. +`sent_messages` within a `RefCell`, and then the `send` method will be able +to modify `sent_messages` to store the messages we’ve seen. Listing 15-22 shows +what that looks like. @@ -227,10 +227,10 @@ For the implementation of the `send` method, the first parameter is still an immutable borrow of `self`, which matches the trait definition. We call `borrow_mut` on the `RefCell>` in `self.sent_messages` to get a mutable reference to the value inside the `RefCell>`, which is the -vector. Then we can call `push` on the mutable reference to the vector to keep +vector. Then, we can call `push` on the mutable reference to the vector to keep track of the messages sent during the test. -The last change we have to make is in the assertion: to see how many items are +The last change we have to make is in the assertion: To see how many items are in the inner vector, we call `borrow` on the `RefCell>` to get an immutable reference to the vector. @@ -272,8 +272,8 @@ at runtime. We create a variable `one_borrow` for the `RefMut` smart pointer returned -from `borrow_mut`. Then we create another mutable borrow in the same way in the -variable `two_borrow`. This makes two mutable references in the same scope, +from `borrow_mut`. Then, we create another mutable borrow in the same way in +the variable `two_borrow`. This makes two mutable references in the same scope, which isn’t allowed. When we run the tests for our library, the code in Listing 15-23 will compile without any errors, but the test will fail: @@ -308,12 +308,13 @@ A common way to use `RefCell` is in combination with `Rc`. Recall that access to that data. If you have an `Rc` that holds a `RefCell`, you can get a value that can have multiple owners _and_ that you can mutate! -For example, recall the cons list example in Listing 15-18 where we used `Rc` -to allow multiple lists to share ownership of another list. Because `Rc` -holds only immutable values, we can’t change any of the values in the list once -we’ve created them. Let’s add in `RefCell` for its ability to change the -values in the lists. Listing 15-24 shows that by using a `RefCell` in the -`Cons` definition, we can modify the value stored in all the lists. +For example, recall the cons list example in Listing 15-18 where we used +`Rc` to allow multiple lists to share ownership of another list. Because +`Rc` holds only immutable values, we can’t change any of the values in the +list once we’ve created them. Let’s add in `RefCell` for its ability to +change the values in the lists. Listing 15-24 shows that by using a +`RefCell` in the `Cons` definition, we can modify the value stored in all +the lists. @@ -324,11 +325,11 @@ values in the lists. Listing 15-24 shows that by using a `RefCell` in the We create a value that is an instance of `Rc>` and store it in a -variable named `value` so we can access it directly later. Then we create a -`List` in `a` with a `Cons` variant that holds `value`. We need to clone -`value` so both `a` and `value` have ownership of the inner `5` value rather -than transferring ownership from `value` to `a` or having `a` borrow from -`value`. +variable named `value` so that we can access it directly later. Then, we create +a `List` in `a` with a `Cons` variant that holds `value`. We need to clone +`value` so that both `a` and `value` have ownership of the inner `5` value +rather than transferring ownership from `value` to `a` or having `a` borrow +from `value`. We wrap the list `a` in an `Rc` so that when we create lists `b` and `c`, they can both refer to `a`, which is what we did in Listing 15-18. @@ -336,7 +337,7 @@ they can both refer to `a`, which is what we did in Listing 15-18. After we’ve created the lists in `a`, `b`, and `c`, we want to add 10 to the value in `value`. We do this by calling `borrow_mut` on `value`, which uses the automatic dereferencing feature we discussed in [“Where’s the `->` -Operator?”][wheres-the---operator]) in Chapter 5 to dereference +Operator?”][wheres-the---operator] in Chapter 5 to dereference the `Rc` to the inner `RefCell` value. The `borrow_mut` method returns a `RefMut` smart pointer, and we use the dereference operator on it and change the inner value. @@ -350,9 +351,9 @@ value of `15` rather than `5`: This technique is pretty neat! By using `RefCell`, we have an outwardly immutable `List` value. But we can use the methods on `RefCell` that provide -access to its interior mutability so we can modify our data when we need to. -The runtime checks of the borrowing rules protect us from data races, and it’s -sometimes worth trading a bit of speed for this flexibility in our data +access to its interior mutability so that we can modify our data when we need +to. The runtime checks of the borrowing rules protect us from data races, and +it’s sometimes worth trading a bit of speed for this flexibility in our data structures. Note that `RefCell` does not work for multithreaded code! `Mutex` is the thread-safe version of `RefCell`, and we’ll discuss `Mutex` in Chapter 16. diff --git a/src/ch15-06-reference-cycles.md b/src/ch15-06-reference-cycles.md index f185ae7801..1c11b02a4d 100644 --- a/src/ch15-06-reference-cycles.md +++ b/src/ch15-06-reference-cycles.md @@ -4,7 +4,7 @@ Rust’s memory safety guarantees make it difficult, but not impossible, to accidentally create memory that is never cleaned up (known as a _memory leak_). Preventing memory leaks entirely is not one of Rust’s guarantees, meaning memory leaks are memory safe in Rust. We can see that Rust allows memory leaks -by using `Rc` and `RefCell`: it’s possible to create references where +by using `Rc` and `RefCell`: It’s possible to create references where items refer to each other in a cycle. This creates memory leaks because the reference count of each item in the cycle will never reach 0, and the values will never be dropped. @@ -15,10 +15,10 @@ Let’s look at how a reference cycle might happen and how to prevent it, starting with the definition of the `List` enum and a `tail` method in Listing 15-25. -+ ```rust -{{#rustdoc_include ../listings/ch15-smart-pointers/listing-15-25/src/main.rs}} +{{#rustdoc_include ../listings/ch15-smart-pointers/listing-15-25/src/main.rs:here}} ``` @@ -32,7 +32,7 @@ second item if we have a `Cons` variant. In Listing 15-26, we’re adding a `main` function that uses the definitions in Listing 15-25. This code creates a list in `a` and a list in `b` that points to -the list in `a`. Then it modifies the list in `a` to point to `b`, creating a +the list in `a`. Then, it modifies the list in `a` to point to `b`, creating a reference cycle. There are `println!` statements along the way to show what the reference counts are at various points in this process. @@ -46,14 +46,14 @@ reference counts are at various points in this process. We create an `Rc` instance holding a `List` value in the variable `a` with an initial list of `5, Nil`. We then create an `Rc` instance holding -another `List` value in the variable `b` that contains the value `10` and points -to the list in `a`. +another `List` value in the variable `b` that contains the value `10` and +points to the list in `a`. -We modify `a` so it points to `b` instead of `Nil`, creating a cycle. We do -that by using the `tail` method to get a reference to the `RefCell>` -in `a`, which we put in the variable `link`. Then we use the `borrow_mut` -method on the `RefCell>` to change the value inside from an `Rc` -that holds a `Nil` value to the `Rc` in `b`. +We modify `a` so that it points to `b` instead of `Nil`, creating a cycle. We +do that by using the `tail` method to get a reference to the +`RefCell>` in `a`, which we put in the variable `link`. Then, we use +the `borrow_mut` method on the `RefCell>` to change the value inside +from an `Rc` that holds a `Nil` value to the `Rc` in `b`. When we run this code, keeping the last `println!` commented out for the moment, we’ll get this output: @@ -64,29 +64,29 @@ moment, we’ll get this output: The reference count of the `Rc` instances in both `a` and `b` is 2 after we change the list in `a` to point to `b`. At the end of `main`, Rust drops the -variable `b`, which decreases the reference count of the `b` `Rc` instance -from 2 to 1. The memory that `Rc` has on the heap won’t be dropped at -this point because its reference count is 1, not 0. Then Rust drops `a`, which -decreases the reference count of the `a` `Rc` instance from 2 to 1 as -well. This instance’s memory can’t be dropped either, because the other +variable `b`, which decreases the reference count of the `b` `Rc` +instance from 2 to 1. The memory that `Rc` has on the heap won’t be +dropped at this point because its reference count is 1, not 0. Then, Rust drops +`a`, which decreases the reference count of the `a` `Rc` instance from 2 +to 1 as well. This instance’s memory can’t be dropped either, because the other `Rc` instance still refers to it. The memory allocated to the list will -remain uncollected forever. To visualize this reference cycle, we’ve created the -diagram in Figure 15-4. +remain uncollected forever. To visualize this reference cycle, we’ve created +the diagram in Figure 15-4. -A rectangle labeled 'a' that points to a rectangle containing the integer 5. A rectangle labeled 'b' that points to a rectangle containing the integer 10. The rectangle containing 5 points to the rectangle containing 10, and the rectangle containing 10 points back to the rectangle containing 5, creating a cycle +A rectangle labeled 'a' that points to a rectangle containing the integer 5. A rectangle labeled 'b' that points to a rectangle containing the integer 10. The rectangle containing 5 points to the rectangle containing 10, and the rectangle containing 10 points back to the rectangle containing 5, creating a cycle. Figure 15-4: A reference cycle of lists `a` and `b` pointing to each other -If you uncomment the last `println!` and run the program, Rust will try to print -this cycle with `a` pointing to `b` pointing to `a` and so forth until it +If you uncomment the last `println!` and run the program, Rust will try to +print this cycle with `a` pointing to `b` pointing to `a` and so forth until it overflows the stack. -Compared to a real-world program, the consequences of creating a reference cycle -in this example aren’t very dire: right after we create the reference cycle, -the program ends. However, if a more complex program allocated lots of memory -in a cycle and held onto it for a long time, the program would use more memory -than it needed and might overwhelm the system, causing it to run out of +Compared to a real-world program, the consequences of creating a reference +cycle in this example aren’t very dire: Right after we create the reference +cycle, the program ends. However, if a more complex program allocated lots of +memory in a cycle and held onto it for a long time, the program would use more +memory than it needed and might overwhelm the system, causing it to run out of available memory. Creating reference cycles is not easily done, but it’s not impossible either. @@ -113,15 +113,16 @@ reference cycles. ### Preventing Reference Cycles Using `Weak` -So far, we’ve demonstrated that calling `Rc::clone` increases the `strong_count` -of an `Rc` instance, and an `Rc` instance is only cleaned up if its -`strong_count` is 0. You can also create a weak reference to the value within -an `Rc` instance by calling `Rc::downgrade` and passing a reference to the -`Rc`. _Strong references_ are how you can share ownership of an `Rc` -instance. _Weak references_ don’t express an ownership relationship, and their -count doesn’t affect when an `Rc` instance is cleaned up. They won’t cause a -reference cycle because any cycle involving some weak references will be broken -once the strong reference count of values involved is 0. +So far, we’ve demonstrated that calling `Rc::clone` increases the +`strong_count` of an `Rc` instance, and an `Rc` instance is only cleaned +up if its `strong_count` is 0. You can also create a weak reference to the +value within an `Rc` instance by calling `Rc::downgrade` and passing a +reference to the `Rc`. *Strong references* are how you can share ownership +of an `Rc` instance. *Weak references* don’t express an ownership +relationship, and their count doesn’t affect when an `Rc` instance is +cleaned up. They won’t cause a reference cycle, because any cycle involving +some weak references will be broken once the strong reference count of values +involved is 0. When you call `Rc::downgrade`, you get a smart pointer of type `Weak`. Instead of increasing the `strong_count` in the `Rc` instance by 1, calling @@ -140,8 +141,8 @@ Rust will ensure that the `Some` case and the `None` case are handled, and there won’t be an invalid pointer. As an example, rather than using a list whose items know only about the next -item, we’ll create a tree whose items know about their children items _and_ -their parent items. +item, we’ll create a tree whose items know about their child items _and_ their +parent items. @@ -151,7 +152,7 @@ their parent items. To start, we’ll build a tree with nodes that know about their child nodes. We’ll create a struct named `Node` that holds its own `i32` value as well as -references to its children `Node` values: +references to its child `Node` values: Filename: src/main.rs @@ -160,8 +161,8 @@ references to its children `Node` values: ``` We want a `Node` to own its children, and we want to share that ownership with -variables so we can access each `Node` in the tree directly. To do this, we -define the `Vec` items to be values of type `Rc`. We also want to +variables so that we can access each `Node` in the tree directly. To do this, +we define the `Vec` items to be values of type `Rc`. We also want to modify which nodes are children of another node, so we have a `RefCell` in `children` around the `Vec>`. @@ -194,11 +195,11 @@ create a reference cycle with `leaf.parent` pointing to `branch` and values to never be 0. Thinking about the relationships another way, a parent node should own its -children: if a parent node is dropped, its child nodes should be dropped as -well. However, a child should not own its parent: if we drop a child node, the +children: If a parent node is dropped, its child nodes should be dropped as +well. However, a child should not own its parent: If we drop a child node, the parent should still exist. This is a case for weak references! -So instead of `Rc`, we’ll make the type of `parent` use `Weak`, +So, instead of `Rc`, we’ll make the type of `parent` use `Weak`, specifically a `RefCell>`. Now our `Node` struct definition looks like this: @@ -208,8 +209,8 @@ like this: {{#rustdoc_include ../listings/ch15-smart-pointers/listing-15-28/src/main.rs:here}} ``` -A node will be able to refer to its parent node but doesn’t own its parent. -In Listing 15-28, we update `main` to use this new definition so the `leaf` +A node will be able to refer to its parent node but doesn’t own its parent. In +Listing 15-28, we update `main` to use this new definition so that the `leaf` node will have a way to refer to its parent, `branch`. @@ -233,16 +234,15 @@ leaf parent = None ``` When we create the `branch` node, it will also have a new `Weak` -reference in the `parent` field because `branch` doesn’t have a parent node. -We still have `leaf` as one of the children of `branch`. Once we have the -`Node` instance in `branch`, we can modify `leaf` to give it a `Weak` -reference to its parent. We use the `borrow_mut` method on the -`RefCell>` in the `parent` field of `leaf`, and then we use the -`Rc::downgrade` function to create a `Weak` reference to `branch` from -the `Rc` in `branch`. +reference in the `parent` field because `branch` doesn’t have a parent node. We +still have `leaf` as one of the children of `branch`. Once we have the `Node` +instance in `branch`, we can modify `leaf` to give it a `Weak` reference +to its parent. We use the `borrow_mut` method on the `RefCell>` in +the `parent` field of `leaf`, and then we use the `Rc::downgrade` function to +create a `Weak` reference to `branch` from the `Rc` in `branch`. When we print the parent of `leaf` again, this time we’ll get a `Some` variant -holding `branch`: now `leaf` can access its parent! When we print `leaf`, we +holding `branch`: Now `leaf` can access its parent! When we print `leaf`, we also avoid the cycle that eventually ended in a stack overflow like we had in Listing 15-26; the `Weak` references are printed as `(Weak)`: @@ -278,7 +278,7 @@ count of 0. In the inner scope, we create `branch` and associate it with will have a strong count of 1 and a weak count of 1 (for `leaf.parent` pointing to `branch` with a `Weak`). When we print the counts in `leaf`, we’ll see it will have a strong count of 2 because `branch` now has a clone of the -`Rc` of `leaf` stored in `branch.children`, but will still have a weak +`Rc` of `leaf` stored in `branch.children` but will still have a weak count of 0. When the inner scope ends, `branch` goes out of scope and the strong count of @@ -304,7 +304,7 @@ This chapter covered how to use smart pointers to make different guarantees and trade-offs from those Rust makes by default with regular references. The `Box` type has a known size and points to data allocated on the heap. The `Rc` type keeps track of the number of references to data on the heap so -that data can have multiple owners. The `RefCell` type with its interior +that the data can have multiple owners. The `RefCell` type with its interior mutability gives us a type that we can use when we need an immutable type but need to change an inner value of that type; it also enforces the borrowing rules at runtime instead of at compile time. diff --git a/src/ch16-00-concurrency.md b/src/ch16-00-concurrency.md index 8b5adcf61a..95b1562f1d 100644 --- a/src/ch16-00-concurrency.md +++ b/src/ch16-00-concurrency.md @@ -5,7 +5,7 @@ major goals. _Concurrent programming_, in which different parts of a program execute independently, and _parallel programming_, in which different parts of a program execute at the same time, are becoming increasingly important as more computers take advantage of their multiple processors. Historically, -programming in these contexts has been difficult and error prone. Rust hopes to +programming in these contexts has been difficult and error-prone. Rust hopes to change that. Initially, the Rust team thought that ensuring memory safety and preventing diff --git a/src/ch16-01-threads.md b/src/ch16-01-threads.md index 5fd154f964..6a1e3d4063 100644 --- a/src/ch16-01-threads.md +++ b/src/ch16-01-threads.md @@ -71,7 +71,7 @@ hi number 5 from the spawned thread! The calls to `thread::sleep` force a thread to stop its execution for a short duration, allowing a different thread to run. The threads will probably take -turns, but that isn’t guaranteed: it depends on how your operating system +turns, but that isn’t guaranteed: It depends on how your operating system schedules the threads. In this run, the main thread printed first, even though the print statement from the spawned thread appears first in the code. And even though we told the spawned thread to print until `i` is `9`, it only got to `5` @@ -184,7 +184,7 @@ another. In [“Capturing References or Moving Ownership”][capture] for more details. At this point, you just need to know that atomics work like primitive types but are safe to share across threads. @@ -223,7 +223,7 @@ Note that if you are doing simple numerical operations, there are types simpler than `Mutex` types provided by the [`std::sync::atomic` module of the standard library][atomic]. These types provide safe, concurrent, atomic access to primitive types. We chose to use `Mutex` with a primitive -type for this example so we could concentrate on how `Mutex` works. +type for this example so that we could concentrate on how `Mutex` works. @@ -231,11 +231,11 @@ type for this example so we could concentrate on how `Mutex` works. ### Comparing `RefCell`/`Rc` and `Mutex`/`Arc` -You might have noticed that `counter` is immutable but we could get a mutable -reference to the value inside it; this means `Mutex` provides interior -mutability, as the `Cell` family does. In the same way we used `RefCell` in -Chapter 15 to allow us to mutate contents inside an `Rc`, we use `Mutex` -to mutate contents inside an `Arc`. +You might have noticed that `counter` is immutable but that we could get a +mutable reference to the value inside it; this means `Mutex` provides +interior mutability, as the `Cell` family does. In the same way we used +`RefCell` in Chapter 15 to allow us to mutate contents inside an `Rc`, we +use `Mutex` to mutate contents inside an `Arc`. Another detail to note is that Rust can’t protect you from all kinds of logic errors when you use `Mutex`. Recall from Chapter 15 that using `Rc` came @@ -244,7 +244,7 @@ each other, causing memory leaks. Similarly, `Mutex` comes with the risk of creating _deadlocks_. These occur when an operation needs to lock two resources and two threads have each acquired one of the locks, causing them to wait for each other forever. If you’re interested in deadlocks, try creating a Rust -program that has a deadlock; then research deadlock mitigation strategies for +program that has a deadlock; then, research deadlock mitigation strategies for mutexes in any language and have a go at implementing them in Rust. The standard library API documentation for `Mutex` and `MutexGuard` offers useful information. diff --git a/src/ch16-04-extensible-concurrency-sync-and-send.md b/src/ch16-04-extensible-concurrency-sync-and-send.md index 80394de078..a866b6174f 100644 --- a/src/ch16-04-extensible-concurrency-sync-and-send.md +++ b/src/ch16-04-extensible-concurrency-sync-and-send.md @@ -1,10 +1,10 @@ -## Extensible Concurrency with `Send` and `Sync` - +## Extensible Concurrency with `Send` and `Sync` + Interestingly, almost every concurrency feature we’ve talked about so far in this chapter has been part of the standard library, not the language. Your options for handling concurrency are not limited to the language or the @@ -22,7 +22,7 @@ rather than the standard library are the `std::marker` traits `Send` and `Sync`. The `Send` marker trait indicates that ownership of values of the type implementing `Send` can be transferred between threads. Almost every Rust type -implements `Send`, but there are some exceptions, including `Rc`: this +implements `Send`, but there are some exceptions, including `Rc`: This cannot implement `Send` because if you cloned an `Rc` value and tried to transfer ownership of the clone to another thread, both threads might update the reference count at the same time. For this reason, `Rc` is implemented @@ -57,9 +57,8 @@ that it doesn’t implement `Send`. The `RefCell` type (which we talked about in Chapter 15) and the family of related `Cell` types don’t implement `Sync`. The implementation of borrow checking that `RefCell` does at runtime is not thread-safe. The smart pointer `Mutex` implements `Sync` and can be -used to share access with multiple threads, as you saw in [“Sharing a -`Mutex` Between Multiple -Threads”][sharing-a-mutext-between-multiple-threads]. +used to share access with multiple threads, as you saw in [“Shared Access to +`Mutex`”][shared-access]. ### Implementing `Send` and `Sync` Manually Is Unsafe @@ -78,7 +77,7 @@ uphold them. ## Summary -This isn’t the last you’ll see of concurrency in this book: the next chapter +This isn’t the last you’ll see of concurrency in this book: The next chapter focuses on async programming, and the project in Chapter 21 will use the concepts in this chapter in a more realistic situation than the smaller examples discussed here. @@ -96,7 +95,7 @@ code using these solutions won’t end up with data races or invalid references. Once you get your code to compile, you can rest assured that it will happily run on multiple threads without the kinds of hard-to-track-down bugs common in other languages. Concurrent programming is no longer a concept to be afraid of: -go forth and make your programs concurrent, fearlessly! +Go forth and make your programs concurrent, fearlessly! -[sharing-a-mutext-between-multiple-threads]: ch16-03-shared-state.html#sharing-a-mutext-between-multiple-threads +[shared-access]: ch16-03-shared-state.html#shared-access-to-mutext [nomicon]: ../nomicon/index.html diff --git a/src/ch18-01-what-is-oo.md b/src/ch18-01-what-is-oo.md index dd4223da26..482c7fd756 100644 --- a/src/ch18-01-what-is-oo.md +++ b/src/ch18-01-what-is-oo.md @@ -4,7 +4,7 @@ There is no consensus in the programming community about what features a language must have to be considered object oriented. Rust is influenced by many programming paradigms, including OOP; for example, we explored the features that came from functional programming in Chapter 13. Arguably, OOP languages -share certain common characteristics, namely objects, encapsulation, and +share certain common characteristics—namely, objects, encapsulation, and inheritance. Let’s look at what each of those characteristics means and whether Rust supports it. @@ -19,7 +19,7 @@ object-oriented design patterns. It defines OOP in this way: > data and the procedures that operate on that data. The procedures are > typically called **methods** or **operations**. -Using this definition, Rust is object oriented: structs and enums have data, +Using this definition, Rust is object oriented: Structs and enums have data, and `impl` blocks provide methods on structs and enums. Even though structs and enums with methods aren’t _called_ objects, they provide the same functionality, according to the Gang of Four’s definition of objects. @@ -34,7 +34,7 @@ the object’s internals and change data or behavior directly. This enables the programmer to change and refactor an object’s internals without needing to change the code that uses the object. -We discussed how to control encapsulation in Chapter 7: we can use the `pub` +We discussed how to control encapsulation in Chapter 7: We can use the `pub` keyword to decide which modules, types, functions, and methods in our code should be public, and by default everything else is private. For example, we can define a struct `AveragedCollection` that has a field containing a vector @@ -72,7 +72,7 @@ or modify data in an instance of `AveragedCollection`. When an item is added to implementations of each call the private `update_average` method that handles updating the `average` field as well. -We leave the `list` and `average` fields private so there is no way for +We leave the `list` and `average` fields private so that there is no way for external code to add or remove items to or from the `list` field directly; otherwise, the `average` field might become out of sync when the `list` changes. The `average` method returns the value in the `average` field, @@ -107,7 +107,7 @@ can use other solutions in Rust, depending on your reason for reaching for inheritance in the first place. You would choose inheritance for two main reasons. One is for reuse of code: -you can implement particular behavior for one type, and inheritance enables you +You can implement particular behavior for one type, and inheritance enables you to reuse that implementation for a different type. You can do this in a limited way in Rust code using default trait method implementations, which you saw in Listing 10-14 when we added a default implementation of the `summarize` method @@ -134,7 +134,7 @@ each other at runtime if they share certain characteristics. > trait bounds to impose constraints on what those types must provide. This is > sometimes called _bounded parametric polymorphism_. -Rust has chosen a different set of tradeoffs by not offering inheritance. +Rust has chosen a different set of trade-offs by not offering inheritance. Inheritance is often at risk of sharing more code than necessary. Subclasses shouldn’t always share all characteristics of their parent class but will do so with inheritance. This can make a program’s design less flexible. It also @@ -145,5 +145,5 @@ subclass can only inherit from one class), further restricting the flexibility of a program’s design. For these reasons, Rust takes the different approach of using trait objects -instead of inheritance to enable polymorphism. Let’s look at how trait objects -work. +instead of inheritance to achieve polymorphism at runtime. Let’s look at how +trait objects work. diff --git a/src/ch18-02-trait-objects.md b/src/ch18-02-trait-objects.md index d743e96105..aee4e1e142 100644 --- a/src/ch18-02-trait-objects.md +++ b/src/ch18-02-trait-objects.md @@ -1,9 +1,9 @@ -## Using Trait Objects to Abstract over Shared Behavior - +## Using Trait Objects to Abstract over Shared Behavior + In Chapter 8, we mentioned that one limitation of vectors is that they can store elements of only one type. We created a workaround in Listing 8-9 where we defined a `SpreadsheetCell` enum that had variants to hold integers, floats, @@ -19,8 +19,8 @@ through a list of items, calling a `draw` method on each one to draw it to the screen—a common technique for GUI tools. We’ll create a library crate called `gui` that contains the structure of a GUI library. This crate might include some types for people to use, such as `Button` or `TextField`. In addition, -`gui` users will want to create their own types that can be drawn: for -instance, one programmer might add an `Image` and another might add a +`gui` users will want to create their own types that can be drawn: For +instance, one programmer might add an `Image`, and another might add a `SelectBox`. At the time of writing the library, we can’t know and define all the types @@ -41,19 +41,19 @@ allow users to create new types compatible with the library. ### Defining a Trait for Common Behavior -To implement the behavior we want `gui` to have, we’ll define a trait named -`Draw` that will have one method named `draw`. Then we can define a vector that -takes a trait object. A _trait object_ points to both an instance of a type -implementing our specified trait and a table used to look up trait methods on -that type at runtime. We create a trait object by specifying some sort of -pointer, such as an `&` reference or a `Box` smart pointer, then the `dyn` -keyword, and then specifying the relevant trait. (We’ll talk about the reason -trait objects must use a pointer in [“Dynamically Sized Types and the `Sized` -Trait”][dynamically-sized] in Chapter 20.) We can use trait -objects in place of a generic or concrete type. Wherever we use a trait object, -Rust’s type system will ensure at compile time that any value used in that -context will implement the trait object’s trait. Consequently, we don’t need to -know all the possible types at compile time. +To implement the behavior that we want `gui` to have, we’ll define a trait +named `Draw` that will have one method named `draw`. Then, we can define a +vector that takes a trait object. A _trait object_ points to both an instance +of a type implementing our specified trait and a table used to look up trait +methods on that type at runtime. We create a trait object by specifying some +sort of pointer, such as a reference or a `Box` smart pointer, then the +`dyn` keyword, and then specifying the relevant trait. (We’ll talk about the +reason trait objects must use a pointer in [“Dynamically Sized Types and the +`Sized` Trait”][dynamically-sized] in Chapter 20.) We can use +trait objects in place of a generic or concrete type. Wherever we use a trait +object, Rust’s type system will ensure at compile time that any value used in +that context will implement the trait object’s trait. Consequently, we don’t +need to know all the possible types at compile time. We’ve mentioned that, in Rust, we refrain from calling structs and enums “objects” to distinguish them from other languages’ objects. In a struct or @@ -61,7 +61,7 @@ enum, the data in the struct fields and the behavior in `impl` blocks are separated, whereas in other languages, the data and behavior combined into one concept is often labeled an object. Trait objects differ from objects in other languages in that we can’t add data to a trait object. Trait objects aren’t as -generally useful as objects in other languages: their specific purpose is to +generally useful as objects in other languages: Their specific purpose is to allow abstraction across common behavior. Listing 18-3 shows how to define a trait named `Draw` with one method named @@ -184,7 +184,7 @@ means it implements the `draw` method. This concept—of being concerned only with the messages a value responds to rather than the value’s concrete type—is similar to the concept of _duck -typing_ in dynamically typed languages: if it walks like a duck and quacks like +typing_ in dynamically typed languages: If it walks like a duck and quacks like a duck, then it must be a duck! In the implementation of `run` on `Screen` in Listing 18-5, `run` doesn’t need to know what the concrete type of each component is. It doesn’t check whether a component is an instance of a `Button` @@ -229,7 +229,7 @@ didn’t mean to pass and so should pass a different type, or we should implemen Recall in [“Performance of Code Using Generics”][performance-of-code-using-generics] in Chapter 10 our discussion on the monomorphization process performed on generics by the -compiler: the compiler generates nongeneric implementations of functions and +compiler: The compiler generates nongeneric implementations of functions and methods for each concrete type that we use in place of a generic type parameter. The code that results from monomorphization is doing _static dispatch_, which is when the compiler knows what method you’re calling at diff --git a/src/ch18-03-oo-design-patterns.md b/src/ch18-03-oo-design-patterns.md index d7593aaa22..e6e5943861 100644 --- a/src/ch18-03-oo-design-patterns.md +++ b/src/ch18-03-oo-design-patterns.md @@ -7,7 +7,7 @@ changes based on its state. We’re going to work through an example of a blog post struct that has a field to hold its state, which will be a state object from the set “draft,” “review,” or “published.” -The state objects share functionality: in Rust, of course, we use structs and +The state objects share functionality: In Rust, of course, we use structs and traits rather than objects and inheritance. Each state object is responsible for its own behavior and for governing when it should change into another state. The value that holds a state object knows nothing about the different @@ -19,8 +19,8 @@ value holding the state or the code that uses the value. We’ll only need to update the code inside one of the state objects to change its rules or perhaps add more state objects. -First we’re going to implement the state pattern in a more traditional -object-oriented way, then we’ll use an approach that’s a bit more natural in +First, we’re going to implement the state pattern in a more traditional +object-oriented way. Then, we’ll use an approach that’s a bit more natural in Rust. Let’s dig in to incrementally implement a blog post workflow using the state pattern. @@ -29,14 +29,18 @@ The final functionality will look like this: 1. A blog post starts as an empty draft. 1. When the draft is done, a review of the post is requested. 1. When the post is approved, it gets published. -1. Only published blog posts return content to print, so unapproved posts can’t - accidentally be published. +1. Only published blog posts return content to print so that unapproved posts + can’t accidentally be published. Any other changes attempted on a post should have no effect. For example, if we try to approve a draft blog post before we’ve requested a review, the post should remain an unpublished draft. -### A Traditional Object-oriented Attempt + + + + +### Attempting Traditional Object-Oriented Style There are infinite ways to structure code to solve the same problem, each with different trade-offs. This section’s implementation is more of a traditional @@ -47,7 +51,7 @@ in a way that might look less familiar to programmers with object-oriented experience. We’ll compare the two solutions to experience the trade-offs of designing Rust code differently than code in other languages. -Listing 18-11 shows this workflow in code form: this is an example usage of the +Listing 18-11 shows this workflow in code form: This is an example usage of the API we’ll implement in a library crate named `blog`. This won’t compile yet because we haven’t implemented the `blog` crate. @@ -94,7 +98,7 @@ instance of `Post`, as shown in Listing 18-12. We’ll also make a private `State` trait that will define the behavior that all state objects for a `Post` must have. -Then `Post` will hold a trait object of `Box` inside an `Option` +Then, `Post` will hold a trait object of `Box` inside an `Option` in a private field named `state` to hold the state object. You’ll see why the `Option` is necessary in a bit. @@ -145,16 +149,21 @@ so it’s not part of the state pattern. The `add_text` method doesn’t interac with the `state` field at all, but it is part of the behavior we want to support. -#### Ensuring the Content of a Draft Post Is Empty + + + + +#### Ensuring That the Content of a Draft Post Is Empty Even after we’ve called `add_text` and added some content to our post, we still want the `content` method to return an empty string slice because the post is -still in the draft state, as shown on line 7 of Listing 18-11. For now, let’s -implement the `content` method with the simplest thing that will fulfill this -requirement: always returning an empty string slice. We’ll change this later -once we implement the ability to change a post’s state so it can be published. -So far, posts can only be in the draft state, so the post content should always -be empty. Listing 18-14 shows this placeholder implementation. +still in the draft state, as shown by the first `assert_eq!` in Listing 18-11. +For now, let’s implement the `content` method with the simplest thing that will +fulfill this requirement: always returning an empty string slice. We’ll change +this later once we implement the ability to change a post’s state so that it +can be published. So far, posts can only be in the draft state, so the post +content should always be empty. Listing 18-14 shows this placeholder +implementation. @@ -164,14 +173,15 @@ be empty. Listing 18-14 shows this placeholder implementation. -With this added `content` method, everything in Listing 18-11 up to line 7 -works as intended. +With this added `content` method, everything in Listing 18-11 through the first +`assert_eq!` works as intended. + -#### Requesting a Review Changes the Post’s State +#### Requesting a Review, Which Changes the Post’s State Next, we need to add functionality to request a review of a post, which should change its state from `Draft` to `PendingReview`. Listing 18-15 shows this code. @@ -185,7 +195,7 @@ change its state from `Draft` to `PendingReview`. Listing 18-15 shows this code. We give `Post` a public method named `request_review` that will take a mutable -reference to `self`. Then we call an internal `request_review` method on the +reference to `self`. Then, we call an internal `request_review` method on the current state of `Post`, and this second `request_review` method consumes the current state and returns a new state. @@ -194,21 +204,21 @@ implement the trait will now need to implement the `request_review` method. Note that rather than having `self`, `&self`, or `&mut self` as the first parameter of the method, we have `self: Box`. This syntax means the method is only valid when called on a `Box` holding the type. This syntax takes -ownership of `Box`, invalidating the old state so the state value of the -`Post` can transform into a new state. +ownership of `Box`, invalidating the old state so that the state value of +the `Post` can transform into a new state. To consume the old state, the `request_review` method needs to take ownership of the state value. This is where the `Option` in the `state` field of `Post` -comes in: we call the `take` method to take the `Some` value out of the `state` +comes in: We call the `take` method to take the `Some` value out of the `state` field and leave a `None` in its place because Rust doesn’t let us have unpopulated fields in structs. This lets us move the `state` value out of -`Post` rather than borrowing it. Then we’ll set the post’s `state` value to the -result of this operation. +`Post` rather than borrowing it. Then, we’ll set the post’s `state` value to +the result of this operation. We need to set `state` to `None` temporarily rather than setting it directly with code like `self.state = self.state.request_review();` to get ownership of -the `state` value. This ensures `Post` can’t use the old `state` value after -we’ve transformed it into a new state. +the `state` value. This ensures that `Post` can’t use the old `state` value +after we’ve transformed it into a new state. The `request_review` method on `Draft` returns a new, boxed instance of a new `PendingReview` struct, which represents the state when a post is waiting for a @@ -217,14 +227,14 @@ but doesn’t do any transformations. Rather, it returns itself because when we request a review on a post already in the `PendingReview` state, it should stay in the `PendingReview` state. -Now we can start seeing the advantages of the state pattern: the +Now we can start seeing the advantages of the state pattern: The `request_review` method on `Post` is the same no matter its `state` value. Each state is responsible for its own rules. We’ll leave the `content` method on `Post` as is, returning an empty string slice. We can now have a `Post` in the `PendingReview` state as well as in the `Draft` state, but we want the same behavior in the `PendingReview` state. -Listing 18-11 now works up to line 10! +Listing 18-11 now works up to the second `assert_eq!` call! @@ -233,7 +243,7 @@ Listing 18-11 now works up to line 10! #### Adding `approve` to Change `content`'s Behavior -The `approve` method will be similar to the `request_review` method: it will +The `approve` method will be similar to the `request_review` method: It will set `state` to the value that the current state says it should have when that state is approved, as shown in Listing 18-16. @@ -271,7 +281,7 @@ as shown in Listing 18-17. Because the goal is to keep all of these rules inside the structs that implement `State`, we call a `content` method on the value in `state` and pass -the post instance (that is, `self`) as an argument. Then we return the value +the post instance (that is, `self`) as an argument. Then, we return the value that’s returned from using the `content` method on the `state` value. We call the `as_ref` method on the `Option` because we want a reference to the @@ -283,13 +293,13 @@ we can’t move `state` out of the borrowed `&self` of the function parameter. We then call the `unwrap` method, which we know will never panic because we know the methods on `Post` ensure that `state` will always contain a `Some` value when those methods are done. This is one of the cases we talked about in -[“Cases in Which You Have More Information Than the -Compiler”][more-info-than-rustc] in Chapter 9 when we know that -a `None` value is never possible, even though the compiler isn’t able to -understand that. +the [“When You Have More Information Than the +Compiler”][more-info-than-rustc] section of Chapter 9 when we +know that a `None` value is never possible, even though the compiler isn’t able +to understand that. At this point, when we call `content` on the `&Box`, deref coercion -will take effect on the `&` and the `Box` so the `content` method will +will take effect on the `&` and the `Box` so that the `content` method will ultimately be called on the type that implements the `State` trait. That means we need to add `content` to the `State` trait definition, and that is where we’ll put the logic for what content to return depending on which state we @@ -307,7 +317,7 @@ We add a default implementation for the `content` method that returns an empty string slice. That means we don’t need to implement `content` on the `Draft` and `PendingReview` structs. The `Published` struct will override the `content` method and return the value in `post.content`. While convenient, having the -`content` method on `State` determine the `content` of the `Post` is blurring +`content` method on `State` determine the content of the `Post` is blurring the lines between the responsibility of `State` and the responsibility of `Post`. @@ -322,7 +332,7 @@ rules lives in the state objects rather than being scattered throughout `Post`. > ### Why Not An Enum? > -> You may have been wondering why we didn’t use an `enum` with the different +> You may have been wondering why we didn’t use an enum with the different > possible post states as variants. That’s certainly a possible solution; try it > and compare the end results to see which you prefer! One disadvantage of using > an enum is that every place that checks the value of the enum will need a @@ -337,10 +347,10 @@ rules lives in the state objects rather than being scattered throughout `Post`. We’ve shown that Rust is capable of implementing the object-oriented state pattern to encapsulate the different kinds of behavior a post should have in -each state. The methods on `Post` know nothing about the various behaviors. The -way we organized the code, we have to look in only one place to know the -different ways a published post can behave: the implementation of the `State` -trait on the `Published` struct. +each state. The methods on `Post` know nothing about the various behaviors. +Because of the way we organized the code, we have to look in only one place to +know the different ways a published post can behave: the implementation of the +`State` trait on the `Published` struct. If we were to create an alternative implementation that didn’t use the state pattern, we might instead use `match` expressions in the methods on `Post` or @@ -374,9 +384,9 @@ another design pattern. Another downside is that we’ve duplicated some logic. To eliminate some of the duplication, we might try to make default implementations for the `request_review` and `approve` methods on the `State` trait that return `self`. -However, this wouldn’t work: when using `State` as a trait object, the trait +However, this wouldn’t work: When using `State` as a trait object, the trait doesn’t know what the concrete `self` will be exactly, so the return type isn’t -known at compile time. (This is one of the `dyn` compatibility rules mentioned +known at compile time. (This is one of the dyn compatibility rules mentioned earlier.) Other duplication includes the similar implementations of the `request_review` @@ -385,7 +395,7 @@ and `approve` methods on `Post`. Both methods use `Option::take` with the value’s implementation of the same method and set the new value of the `state` field to the result. If we had a lot of methods on `Post` that followed this pattern, we might consider defining a macro to eliminate the repetition (see -[“Macros”][macros] in Chapter 20). +the [“Macros”][macros] section in Chapter 20). By implementing the state pattern exactly as it’s defined for object-oriented languages, we’re not taking as full advantage of Rust’s strengths as we could. @@ -396,9 +406,10 @@ invalid states and transitions into compile-time errors. We’ll show you how to rethink the state pattern to get a different set of trade-offs. Rather than encapsulating the states and transitions completely so -outside code has no knowledge of them, we’ll encode the states into different -types. Consequently, Rust’s type checking system will prevent attempts to use -draft posts where only published posts are allowed by issuing a compiler error. +that outside code has no knowledge of them, we’ll encode the states into +different types. Consequently, Rust’s type-checking system will prevent +attempts to use draft posts where only published posts are allowed by issuing a +compiler error. Let’s consider the first part of `main` in Listing 18-11: @@ -413,14 +424,14 @@ Let’s consider the first part of `main` in Listing 18-11: We still enable the creation of new posts in the draft state using `Post::new` and the ability to add text to the post’s content. But instead of having a `content` method on a draft post that returns an empty string, we’ll make it so -draft posts don’t have the `content` method at all. That way, if we try to get -a draft post’s content, we’ll get a compiler error telling us the method +that draft posts don’t have the `content` method at all. That way, if we try to +get a draft post’s content, we’ll get a compiler error telling us the method doesn’t exist. As a result, it will be impossible for us to accidentally display draft post content in production because that code won’t even compile. Listing 18-19 shows the definition of a `Post` struct and a `DraftPost` struct, as well as methods on each. -+ ```rust,noplayground {{#rustdoc_include ../listings/ch18-oop/listing-18-19/src/lib.rs}} @@ -441,15 +452,15 @@ instance of `Post` right now. The `DraftPost` struct has an `add_text` method, so we can add text to `content` as before, but note that `DraftPost` does not have a `content` method -defined! So now the program ensures all posts start as draft posts, and draft -posts don’t have their content available for display. Any attempt to get around -these constraints will result in a compiler error. +defined! So now the program ensures that all posts start as draft posts, and +draft posts don’t have their content available for display. Any attempt to get +around these constraints will result in a compiler error. -So how do we get a published post? We want to enforce the rule that a draft +So, how do we get a published post? We want to enforce the rule that a draft post has to be reviewed and approved before it can be published. A post in the pending review state should still not display any content. Let’s implement these constraints by adding another struct, `PendingReviewPost`, defining the @@ -481,7 +492,7 @@ But we also have to make some small changes to `main`. The `request_review` and `approve` methods return new instances rather than modifying the struct they’re called on, so we need to add more `let post =` shadowing assignments to save the returned instances. We also can’t have the assertions about the draft and -pending review posts’ contents be empty strings, nor do we need them: we can’t +pending review posts’ contents be empty strings, nor do we need them: We can’t compile code that tries to use the content of posts in those states any longer. The updated code in `main` is shown in Listing 18-21. @@ -495,7 +506,7 @@ The updated code in `main` is shown in Listing 18-21. The changes we needed to make to `main` to reassign `post` mean that this implementation doesn’t quite follow the object-oriented state pattern anymore: -the transformations between the states are no longer encapsulated entirely +The transformations between the states are no longer encapsulated entirely within the `Post` implementation. However, our gain is that invalid states are now impossible because of the type system and the type checking that happens at compile time! This ensures that certain bugs, such as display of the content of diff --git a/src/ch19-00-patterns.md b/src/ch19-00-patterns.md index 4574ac0b8e..ad6976ca77 100644 --- a/src/ch19-00-patterns.md +++ b/src/ch19-00-patterns.md @@ -1,6 +1,6 @@ # Patterns and Matching -_Patterns_ are a special syntax in Rust for matching against the structure of +Patterns are a special syntax in Rust for matching against the structure of types, both complex and simple. Using patterns in conjunction with `match` expressions and other constructs gives you more control over a program’s control flow. A pattern consists of some combination of the following: diff --git a/src/ch19-01-all-the-places-for-patterns.md b/src/ch19-01-all-the-places-for-patterns.md index 2a93e7411a..1a6d21eb54 100644 --- a/src/ch19-01-all-the-places-for-patterns.md +++ b/src/ch19-01-all-the-places-for-patterns.md @@ -32,19 +32,19 @@ match x { } ``` -The patterns in this `match` expression are the `None` and `Some(i)` on the +The patterns in this `match` expression are the `None` and `Some(i)` to the left of each arrow. -One requirement for `match` expressions is that they need to be _exhaustive_ in +One requirement for `match` expressions is that they need to be exhaustive in the sense that all possibilities for the value in the `match` expression must -be accounted for. One way to ensure you’ve covered every possibility is to have -a catch-all pattern for the last arm: for example, a variable name matching any -value can never fail and thus covers every remaining case. +be accounted for. One way to ensure that you’ve covered every possibility is to +have a catch-all pattern for the last arm: For example, a variable name +matching any value can never fail and thus covers every remaining case. The particular pattern `_` will match anything, but it never binds to a variable, so it’s often used in the last match arm. The `_` pattern can be -useful when you want to ignore any value not specified, for example. We’ll cover -the `_` pattern in more detail in [“Ignoring Values in a +useful when you want to ignore any value not specified, for example. We’ll +cover the `_` pattern in more detail in [“Ignoring Values in a Pattern”][ignoring-values-in-a-pattern] later in this chapter. ### `let` Statements @@ -91,8 +91,8 @@ To see the pattern-matching aspect of `let` more clearly, consider Listing Here, we match a tuple against a pattern. Rust compares the value `(1, 2, 3)` -to the pattern `(x, y, z)` and sees that the value matches the pattern, in that -it sees that the number of elements is the same in both, so Rust binds `1` to +to the pattern `(x, y, z)` and sees that the value matches the pattern—that is, +it sees that the number of elements is the same in both—so Rust binds `1` to `x`, `2` to `y`, and `3` to `z`. You can think of this tuple pattern as nesting three individual variable patterns inside it. @@ -119,8 +119,8 @@ To fix the error, we could ignore one or more of the values in the tuple using `_` or `..`, as you’ll see in the [“Ignoring Values in a Pattern”][ignoring-values-in-a-pattern] section. If the problem is that we have too many variables in the pattern, the solution is to make the -types match by removing variables so the number of variables equals the number -of elements in the tuple. +types match by removing variables so that the number of variables equals the +number of elements in the tuple. ### Conditional `if let` Expressions @@ -160,10 +160,10 @@ hardcoded values we have here, this example will print `Using purple as the background color`. You can see that `if let` can also introduce new variables that shadow existing -variables in the same way that `match` arms can: the line `if let Ok(age) = age` +variables in the same way that `match` arms can: The line `if let Ok(age) = age` introduces a new `age` variable that contains the value inside the `Ok` variant, shadowing the existing `age` variable. This means we need to place the `if age > -30` condition within that block: we can’t combine these two conditions into `if +30` condition within that block: We can’t combine these two conditions into `if let Ok(age) = age && age > 30`. The new `age` we want to compare to 30 isn’t valid until the new scope starts with the curly bracket. @@ -176,7 +176,7 @@ not alert us to the possible logic bug. Similar in construction to `if let`, the `while let` conditional loop allows a `while` loop to run for as long as a pattern continues to match. In Listing -19-4 we show a `while let` loop that waits on messages sent between threads, +19-4, we show a `while let` loop that waits on messages sent between threads, but in this case checking a `Result` instead of an `Option`. @@ -190,16 +190,16 @@ but in this case checking a `Result` instead of an `Option`. This example prints `1`, `2`, and then `3`. The `recv` method takes the first message out of the receiver side of the channel and returns an `Ok(value)`. When we first saw `recv` back in Chapter 16, we unwrapped the error directly, or -interacted with it as an iterator using a `for` loop. As Listing 19-4 shows, -though, we can also use while let, because the `recv` method returns an `Ok` +we interacted with it as an iterator using a `for` loop. As Listing 19-4 shows, +though, we can also use `while let`, because the `recv` method returns an `Ok` each time a message arrives, as long as the sender exists, and then produces an -`Err `once the sender side disconnects. +`Err` once the sender side disconnects. ### `for` Loops In a `for` loop, the value that directly follows the keyword `for` is a pattern. For example, in `for x in y`, the `x` is the pattern. Listing 19-5 -demonstrates how to use a pattern in a `for` loop to *destructure*, or break +demonstrates how to use a pattern in a `for` loop to destructure, or break apart, a tuple as part of the `for` loop. @@ -218,11 +218,12 @@ The code in Listing 19-5 will print the following: {{#include ../listings/ch19-patterns-and-matching/listing-19-05/output.txt}} ``` -We adapt an iterator using the `enumerate` method so it produces a value and -the index for that value, placed into a tuple. The first value produced is the -tuple `(0, 'a')`. When this value is matched to the pattern `(index, value)`, -`index` will be `0` and `value` will be `'a'`, printing the first line of the -output. +We adapt an iterator using the `enumerate` method so that it produces a value +and the index for that value, placed into a tuple. The first value produced is +the tuple `(0, 'a')`. When this value is matched to the pattern `(index, +value)`, index will be `0` and value will be `'a'`, printing the first line of +the output. + ### Function Parameters @@ -230,7 +231,7 @@ Function parameters can also be patterns. The code in Listing 19-6, which declares a function named `foo` that takes one parameter named `x` of type `i32`, should by now look familiar. -+ ```rust {{#rustdoc_include ../listings/ch19-patterns-and-matching/listing-19-06/src/main.rs:here}} diff --git a/src/ch19-02-refutability.md b/src/ch19-02-refutability.md index c886efad33..22e1e9e13a 100644 --- a/src/ch19-02-refutability.md +++ b/src/ch19-02-refutability.md @@ -13,12 +13,12 @@ irrefutable patterns because the program cannot do anything meaningful when values don’t match. The `if let` and `while let` expressions and the `let...else` statement accept refutable and irrefutable patterns, but the compiler warns against irrefutable patterns because, by definition, they’re -intended to handle possible failure: the functionality of a conditional is in +intended to handle possible failure: The functionality of a conditional is in its ability to perform differently depending on success or failure. In general, you shouldn’t have to worry about the distinction between refutable and irrefutable patterns; however, you do need to be familiar with the concept -of refutability so you can respond when you see it in an error message. In +of refutability so that you can respond when you see it in an error message. In those cases, you’ll need to change either the pattern or the construct you’re using the pattern with, depending on the intended behavior of the code. @@ -49,7 +49,7 @@ Because we didn’t cover (and couldn’t cover!) every valid value with the pattern `Some(x)`, Rust rightfully produces a compiler error. If we have a refutable pattern where an irrefutable pattern is needed, we can -fix it by changing the code that uses the pattern: instead of using `let`, we +fix it by changing the code that uses the pattern: Instead of using `let`, we can use `let else`. Then, if the pattern doesn’t match, the code will just skip the code in the curly brackets, giving it a way to continue validly. Listing 19-9 shows how to fix the code in Listing 19-8. diff --git a/src/ch19-03-pattern-syntax.md b/src/ch19-03-pattern-syntax.md index 7c0a78672d..1641329504 100644 --- a/src/ch19-03-pattern-syntax.md +++ b/src/ch19-03-pattern-syntax.md @@ -62,8 +62,8 @@ the inner `y`. The last `println!` produces `at the end: x = Some(5), y = 10`. To create a `match` expression that compares the values of the outer `x` and `y`, rather than introducing a new variable that shadows the existing `y` variable, we would need to use a match guard conditional instead. We’ll talk -about match guards later in [“Extra Conditionals with Match -Guards”](#extra-conditionals-with-match-guards). +about match guards later in the [“Adding Conditionals with Match +Guards”](#adding-conditionals-with-match-guards) section. @@ -71,7 +71,7 @@ Guards”](#extra-conditionals-with-match-guards). ### Matching Multiple Patterns In `match` expressions, you can match multiple patterns using the `|` syntax, -which is the pattern _or_ operator. For example, in the following code we match +which is the pattern _or_ operator. For example, in the following code, we match the value of `x` against the match arms, the first of which has an _or_ option, meaning if the value of `x` matches either of the values in that arm, that arm’s code will run: @@ -141,7 +141,7 @@ However, it’s common to match the variable names to the field names to make it easier to remember which variables came from which fields. Because of this common usage, and because writing `let Point { x: x, y: y } = p;` contains a lot of duplication, Rust has a shorthand for patterns that match struct fields: -you only need to list the name of the struct field, and the variables created +You only need to list the name of the struct field, and the variables created from the pattern will have the same names. Listing 19-13 behaves in the same way as the code in Listing 19-12, but the variables created in the `let` pattern are `x` and `y` instead of `a` and `b`. @@ -198,9 +198,9 @@ and the `y` axis, this code would only print `On the x axis at 0`. #### Enums We’ve destructured enums in this book (for example, Listing 6-5 in Chapter 6), -but haven’t yet explicitly discussed that the pattern to destructure an enum +but we haven’t yet explicitly discussed that the pattern to destructure an enum corresponds to the way the data stored within the enum is defined. As an -example, in Listing 19-15 we use the `Message` enum from Listing 6-2 and write +example, in Listing 19-15, we use the `Message` enum from Listing 6-2 and write a `match` with patterns that will destructure each inner value. @@ -220,9 +220,9 @@ and no variables are in that pattern. For struct-like enum variants, such as `Message::Move`, we can use a pattern similar to the pattern we specify to match structs. After the variant name, we -place curly brackets and then list the fields with variables so we break apart -the pieces to use in the code for this arm. Here we use the shorthand form as -we did in Listing 19-13. +place curly brackets and then list the fields with variables so that we break +apart the pieces to use in the code for this arm. Here we use the shorthand +form as we did in Listing 19-13. For tuple-like enum variants, like `Message::Write` that holds a tuple with one element and `Message::ChangeColor` that holds a tuple with three elements, the @@ -250,7 +250,7 @@ message, as shown in Listing 19-16. The pattern of the first arm in the `match` expression matches a -`Message::ChangeColor` enum variant that contains a `Color::Rgb` variant; then +`Message::ChangeColor` enum variant that contains a `Color::Rgb` variant; then, the pattern binds to the three inner `i32` values. The pattern of the second arm also matches a `Message::ChangeColor` enum variant, but the inner enum matches `Color::Hsv` instead. We can specify these complex conditions in one @@ -270,8 +270,8 @@ tuples inside a tuple and destructure all the primitive values out: {{#rustdoc_include ../listings/ch19-patterns-and-matching/no-listing-05-destructuring-structs-and-tuples/src/main.rs:here}} ``` -This code lets us break complex types into their component parts so we can use -the values we’re interested in separately. +This code lets us break complex types into their component parts so that we can +use the values we’re interested in separately. Destructuring with patterns is a convenient way to use pieces of values, such as the value from each field in a struct, separately from each other. @@ -309,12 +309,14 @@ This code will completely ignore the value `3` passed as the first argument, and will print `This code only uses the y parameter: 4`. In most cases when you no longer need a particular function parameter, you -would change the signature so it doesn’t include the unused parameter. Ignoring -a function parameter can be especially useful in cases when, for example, -you’re implementing a trait when you need a certain type signature but the -function body in your implementation doesn’t need one of the parameters. You -then avoid getting a compiler warning about unused function parameters, as you -would if you used a name instead. +would change the signature so that it doesn’t include the unused parameter. +Ignoring a function parameter can be especially useful in cases when, for +example, you’re implementing a trait when you need a certain type signature but +the function body in your implementation doesn’t need one of the parameters. +You then avoid getting a compiler warning about unused function parameters, as +you would if you used a name instead. + + @@ -505,7 +507,7 @@ guard of `if x % 2 == 0` (which will be `true` if the number is even). This example will print `The number 4 is even`. When `num` is compared to the -pattern in the first arm, it matches because `Some(4)` matches `Some(x)`. Then +pattern in the first arm, it matches because `Some(4)` matches `Some(x)`. Then, the match guard checks whether the remainder of dividing `x` by 2 is equal to 0, and because it is, the first arm is selected. @@ -519,12 +521,12 @@ the match guard gives us the ability to express this logic. The downside of this additional expressiveness is that the compiler doesn’t try to check for exhaustiveness when match guard expressions are involved. -In Listing 19-11, we mentioned that we could use match guards to solve our -pattern-shadowing problem. Recall that we created a new variable inside the -pattern in the `match` expression instead of using the variable outside the -`match`. That new variable meant we couldn’t test against the value of the -outer variable. Listing 19-27 shows how we can use a match guard to fix this -problem. +When discussing Listing 19-11, we mentioned that we could use match guards to +solve our pattern-shadowing problem. Recall that we created a new variable +inside the pattern in the `match` expression instead of using the variable +outside the `match`. That new variable meant we couldn’t test against the value +of the outer variable. Listing 19-27 shows how we can use a match guard to fix +this problem. @@ -580,9 +582,9 @@ rather than this: 4 | 5 | (6 if y) => ... ``` -After running the code, the precedence behavior is evident: if the match guard +After running the code, the precedence behavior is evident: If the match guard were applied only to the final value in the list of values specified using the -`|` operator, the arm would have matched and the program would have printed +`|` operator, the arm would have matched, and the program would have printed `yes`. @@ -594,7 +596,7 @@ were applied only to the final value in the list of values specified using the The _at_ operator `@` lets us create a variable that holds a value at the same time we’re testing that value for a pattern match. In Listing 19-29, we want to test that a `Message::Hello` `id` field is within the range `3..=7`. We also -want to bind the value to the variable `id` so we can use it in the code +want to bind the value to the variable `id` so that we can use it in the code associated with the arm. @@ -613,24 +615,24 @@ In the second arm, where we only have a range specified in the pattern, the code associated with the arm doesn’t have a variable that contains the actual value of the `id` field. The `id` field’s value could have been 10, 11, or 12, but the code that goes with that pattern doesn’t know which it is. The pattern code -isn’t able to use the value from the `id` field, because we haven’t saved the +isn’t able to use the value from the `id` field because we haven’t saved the `id` value in a variable. In the last arm, where we’ve specified a variable without a range, we do have the value available to use in the arm’s code in a variable named `id`. The reason is that we’ve used the struct field shorthand syntax. But we haven’t applied any test to the value in the `id` field in this arm, as we did with the -first two arms: any value would match this pattern. +first two arms: Any value would match this pattern. Using `@` lets us test a value and save it in a variable within one pattern. ## Summary Rust’s patterns are very useful in distinguishing between different kinds of -data. When used in `match` expressions, Rust ensures your patterns cover every -possible value, or your program won’t compile. Patterns in `let` statements and -function parameters make those constructs more useful, enabling the -destructuring of values into smaller parts and assigning those parts to +data. When used in `match` expressions, Rust ensures that your patterns cover +every possible value, or your program won’t compile. Patterns in `let` +statements and function parameters make those constructs more useful, enabling +the destructuring of values into smaller parts and assigning those parts to variables. We can create simple or complex patterns to suit our needs. Next, for the penultimate chapter of the book, we’ll look at some advanced diff --git a/src/ch20-00-advanced-features.md b/src/ch20-00-advanced-features.md index 66e558eef7..6754bce3f1 100644 --- a/src/ch20-00-advanced-features.md +++ b/src/ch20-00-advanced-features.md @@ -2,7 +2,7 @@ By now, you’ve learned the most commonly used parts of the Rust programming language. Before we do one more project, in Chapter 21, we’ll look at a few -aspects of the language you might run into every once in a while, but may not +aspects of the language you might run into every once in a while but may not use every day. You can use this chapter as a reference for when you encounter any unknowns. The features covered here are useful in very specific situations. Although you might not reach for them often, we want to make sure you have a @@ -10,13 +10,13 @@ grasp of all the features Rust has to offer. In this chapter, we’ll cover: -- Unsafe Rust: how to opt out of some of Rust’s guarantees and take +- Unsafe Rust: How to opt out of some of Rust’s guarantees and take responsibility for manually upholding those guarantees -- Advanced traits: associated types, default type parameters, fully qualified +- Advanced traits: Associated types, default type parameters, fully qualified syntax, supertraits, and the newtype pattern in relation to traits -- Advanced types: more about the newtype pattern, type aliases, the never type, +- Advanced types: More about the newtype pattern, type aliases, the never type, and dynamically sized types -- Advanced functions and closures: function pointers and returning closures -- Macros: ways to define code that defines more code at compile time +- Advanced functions and closures: Function pointers and returning closures +- Macros: Ways to define code that defines more code at compile time It’s a panoply of Rust features with something for everyone! Let’s dive in! diff --git a/src/ch20-01-unsafe-rust.md b/src/ch20-01-unsafe-rust.md index 60dfc5d1dc..12f9fb6f27 100644 --- a/src/ch20-01-unsafe-rust.md +++ b/src/ch20-01-unsafe-rust.md @@ -2,8 +2,8 @@ All the code we’ve discussed so far has had Rust’s memory safety guarantees enforced at compile time. However, Rust has a second language hidden inside it -that doesn’t enforce these memory safety guarantees: it’s called _unsafe Rust_ -and works just like regular Rust, but gives us extra superpowers. +that doesn’t enforce these memory safety guarantees: It’s called _unsafe Rust_ +and works just like regular Rust but gives us extra superpowers. Unsafe Rust exists because, by nature, static analysis is conservative. When the compiler tries to determine whether or not code upholds the guarantees, @@ -11,7 +11,7 @@ it’s better for it to reject some valid programs than to accept some invalid programs. Although the code _might_ be okay, if the Rust compiler doesn’t have enough information to be confident, it will reject the code. In these cases, you can use unsafe code to tell the compiler, “Trust me, I know what I’m -doing.” Be warned, however, that you use unsafe Rust at your own risk: if you +doing.” Be warned, however, that you use unsafe Rust at your own risk: If you use unsafe code incorrectly, problems can occur due to memory unsafety, such as null pointer dereferencing. @@ -34,22 +34,22 @@ that holds the unsafe code. You can take five actions in unsafe Rust that you can’t in safe Rust, which we call _unsafe superpowers_. Those superpowers include the ability to: -1. Dereference a raw pointer -1. Call an unsafe function or method -1. Access or modify a mutable static variable -1. Implement an unsafe trait -1. Access fields of `union`s +1. Dereference a raw pointer. +1. Call an unsafe function or method. +1. Access or modify a mutable static variable. +1. Implement an unsafe trait. +1. Access fields of `union`s. It’s important to understand that `unsafe` doesn’t turn off the borrow checker -or disable any of Rust’s other safety checks: if you use a reference in unsafe +or disable any of Rust’s other safety checks: If you use a reference in unsafe code, it will still be checked. The `unsafe` keyword only gives you access to these five features that are then not checked by the compiler for memory safety. You’ll still get some degree of safety inside an unsafe block. In addition, `unsafe` does not mean the code inside the block is necessarily -dangerous or that it will definitely have memory safety problems: the intent is -that as the programmer, you’ll ensure the code inside an `unsafe` block will -access memory in a valid way. +dangerous or that it will definitely have memory safety problems: The intent is +that as the programmer, you’ll ensure that the code inside an `unsafe` block +will access memory in a valid way. People are fallible and mistakes will happen, but by requiring these five unsafe operations to be inside blocks annotated with `unsafe`, you’ll know that @@ -71,13 +71,14 @@ some abstractions that provide a safe interface to unsafe code. ### Dereferencing a Raw Pointer -In Chapter 4, in [“Dangling References”][dangling-references], we -mentioned that the compiler ensures references are always valid. Unsafe Rust has -two new types called _raw pointers_ that are similar to references. As with -references, raw pointers can be immutable or mutable and are written as `*const -T` and `*mut T`, respectively. The asterisk isn’t the dereference operator; it’s -part of the type name. In the context of raw pointers, _immutable_ means that -the pointer can’t be directly assigned to after being dereferenced. +In Chapter 4, in the [“Dangling References”][dangling-references] section, we mentioned that the compiler ensures that references are always +valid. Unsafe Rust has two new types called _raw pointers_ that are similar to +references. As with references, raw pointers can be immutable or mutable and +are written as `*const T` and `*mut T`, respectively. The asterisk isn’t the +dereference operator; it’s part of the type name. In the context of raw +pointers, _immutable_ means that the pointer can’t be directly assigned to +after being dereferenced. Different from references and smart pointers, raw pointers: @@ -114,9 +115,9 @@ that assumption about just any raw pointer. To demonstrate this, next we’ll create a raw pointer whose validity we can’t be so certain of, using the keyword `as` to cast a value instead of using the raw borrow operator. Listing 20-2 shows how to create a raw pointer to an arbitrary -location in memory. Trying to use arbitrary memory is undefined: there might be +location in memory. Trying to use arbitrary memory is undefined: There might be data at that address or there might not, the compiler might optimize the code -so there is no memory access, or the program might terminate with a +so that there is no memory access, or the program might terminate with a segmentation fault. Usually, there is no good reason to write code like this, especially in cases where you can use a raw borrow operator instead, but it is possible. @@ -129,7 +130,7 @@ possible. -Recall that we can create raw pointers in safe code, but we can’t _dereference_ +Recall that we can create raw pointers in safe code, but we can’t dereference raw pointers and read the data being pointed to. In Listing 20-3, we use the dereference operator `*` on a raw pointer that requires an `unsafe` block. @@ -200,7 +201,7 @@ Just because a function contains unsafe code doesn’t mean we need to mark the entire function as unsafe. In fact, wrapping unsafe code in a safe function is a common abstraction. As an example, let’s study the `split_at_mut` function from the standard library, which requires some unsafe code. We’ll explore how -we might implement it. This safe method is defined on mutable slices: it takes +we might implement it. This safe method is defined on mutable slices: It takes one slice and makes it two by splitting the slice at the index given as an argument. Listing 20-4 shows how to use `split_at_mut`. @@ -225,13 +226,13 @@ of `i32` values rather than for a generic type `T`. -This function first gets the total length of the slice. Then it asserts that +This function first gets the total length of the slice. Then, it asserts that the index given as a parameter is within the slice by checking whether it’s less than or equal to the length. The assertion means that if we pass an index that is greater than the length to split the slice at, the function will panic before it attempts to use that index. -Then we return two mutable slices in a tuple: one from the start of the +Then, we return two mutable slices in a tuple: one from the start of the original slice to the `mid` index and another from `mid` to the end of the slice. @@ -258,26 +259,26 @@ to unsafe functions to make the implementation of `split_at_mut` work. -Recall from [“The Slice Type”][the-slice-type] in Chapter 4 that -a slice is a pointer to some data and the length of the slice. We use the `len` -method to get the length of a slice and the `as_mut_ptr` method to access the -raw pointer of a slice. In this case, because we have a mutable slice to `i32` -values, `as_mut_ptr` returns a raw pointer with the type `*mut i32`, which we’ve -stored in the variable `ptr`. +Recall from [“The Slice Type”][the-slice-type] section in +Chapter 4 that a slice is a pointer to some data and the length of the slice. +We use the `len` method to get the length of a slice and the `as_mut_ptr` +method to access the raw pointer of a slice. In this case, because we have a +mutable slice to `i32` values, `as_mut_ptr` returns a raw pointer with the type +`*mut i32`, which we’ve stored in the variable `ptr`. -We keep the assertion that the `mid` index is within the slice. Then we get to -the unsafe code: the `slice::from_raw_parts_mut` function takes a raw pointer +We keep the assertion that the `mid` index is within the slice. Then, we get to +the unsafe code: The `slice::from_raw_parts_mut` function takes a raw pointer and a length, and it creates a slice. We use this function to create a slice -that starts from `ptr` and is `mid` items long. Then we call the `add` -method on `ptr` with `mid` as an argument to get a raw pointer that starts at -`mid`, and we create a slice using that pointer and the remaining number of -items after `mid` as the length. +that starts from `ptr` and is `mid` items long. Then, we call the `add` method +on `ptr` with `mid` as an argument to get a raw pointer that starts at `mid`, +and we create a slice using that pointer and the remaining number of items +after `mid` as the length. The function `slice::from_raw_parts_mut` is unsafe because it takes a raw pointer and must trust that this pointer is valid. The `add` method on raw pointers is also unsafe because it must trust that the offset location is also a valid pointer. Therefore, we had to put an `unsafe` block around our calls to -`slice::from_raw_parts_mut` and `add` so we could call them. By looking at +`slice::from_raw_parts_mut` and `add` so that we could call them. By looking at the code and by adding the assertion that `mid` must be less than or equal to `len`, we can tell that all the raw pointers used within the `unsafe` block will be valid pointers to data within the slice. This is an acceptable and @@ -331,13 +332,13 @@ programmer to ensure safety. Within the `unsafe extern "C"` block, we list the names and signatures of external functions from another language we want to call. The `"C"` part defines which _application binary interface (ABI)_ the external function uses: -the ABI defines how to call the function at the assembly level. The `"C"` ABI +The ABI defines how to call the function at the assembly level. The `"C"` ABI is the most common and follows the C programming language’s ABI. Information about all the ABIs Rust supports is available in [the Rust Reference][ABI]. Every item declared within an `unsafe extern` block is implicitly unsafe. However, some FFI functions *are* safe to call. For example, the `abs` function -from C’s standard library does not have any memory safety considerations and we +from C’s standard library does not have any memory safety considerations, and we know it can be called with any `i32`. In cases like this, we can use the `safe` keyword to say that this specific function is safe to call even though it is in an `unsafe extern` block. Once we make that change, calling it no longer @@ -387,8 +388,9 @@ This usage of `extern` requires `unsafe` only in the attribute, not on the ### Accessing or Modifying a Mutable Static Variable In this book, we’ve not yet talked about global variables, which Rust does -support but can be problematic with Rust’s ownership rules. If two threads are -accessing the same mutable global variable, it can cause a data race. +support but which can be problematic with Rust’s ownership rules. If two +threads are accessing the same mutable global variable, it can cause a data +race. In Rust, global variables are called _static_ variables. Listing 20-10 shows an example declaration and use of a static variable with a string slice as a @@ -402,13 +404,12 @@ value. -Static variables are similar to constants, which we discussed in -[“Constants”][differences-between-variables-and-constants] in -Chapter 3. The names of static variables are in `SCREAMING_SNAKE_CASE` by -convention. Static variables can only store references with the `'static` -lifetime, which means the Rust compiler can figure out the lifetime and we -aren’t required to annotate it explicitly. Accessing an immutable static -variable is safe. +Static variables are similar to constants, which we discussed in the +[“Declaring Constants”][constants] section in Chapter 3. The +names of static variables are in `SCREAMING_SNAKE_CASE` by convention. Static +variables can only store references with the `'static` lifetime, which means +the Rust compiler can figure out the lifetime and we aren’t required to +annotate it explicitly. Accessing an immutable static variable is safe. A subtle difference between constants and immutable static variables is that values in a static variable have a fixed address in memory. Using the value @@ -431,7 +432,7 @@ code that reads or writes from `COUNTER` must be within an `unsafe` block. The code in Listing 20-11 compiles and prints `COUNTER: 3` as we would expect because it’s single threaded. Having multiple threads access `COUNTER` would likely result in data races, so it is undefined behavior. Therefore, we need to -mark the entire function as `unsafe` and document the safety limitation, so +mark the entire function as `unsafe` and document the safety limitation so that anyone calling the function knows what they are and are not allowed to do safely. @@ -443,7 +444,7 @@ rules are upheld. Additionally, the compiler will deny by default any attempt to create references to a mutable static variable through a compiler lint. You must -either explicitly opt-out of that lint’s protections by adding an +either explicitly opt out of that lint’s protections by adding an `#[allow(static_mut_refs)]` annotation or access the mutable static variable via a raw pointer created with one of the raw borrow operators. That includes cases where the reference is created invisibly, as when it is used in the @@ -451,11 +452,11 @@ cases where the reference is created invisibly, as when it is used in the variables to be created via raw pointers helps make the safety requirements for using them more obvious. -With mutable data that is globally accessible, it’s difficult to ensure there -are no data races, which is why Rust considers mutable static variables to be -unsafe. Where possible, it’s preferable to use the concurrency techniques and -thread-safe smart pointers we discussed in Chapter 16 so the compiler checks -that data access from different threads is done safely. +With mutable data that is globally accessible, it’s difficult to ensure that +there are no data races, which is why Rust considers mutable static variables +to be unsafe. Where possible, it’s preferable to use the concurrency techniques +and thread-safe smart pointers we discussed in Chapter 16 so that the compiler +checks that data access from different threads is done safely. ### Implementing an Unsafe Trait @@ -476,17 +477,16 @@ Listing 20-12. By using `unsafe impl`, we’re promising that we’ll uphold the invariants that the compiler can’t verify. -As an example, recall the `Send` and `Sync` marker traits we discussed in -[“Extensible Concurrency with the `Send` and `Sync` -Traits”][extensible-concurrency-with-the-send-and-sync-traits] -in Chapter 16: the compiler implements these traits automatically if our types -are composed entirely of other types that implement `Send` and `Sync`. If we -implement a type that contains a type that does not implement `Send` or `Sync`, -such as raw pointers, and we want to mark that type as `Send` or `Sync`, we -must use `unsafe`. Rust can’t verify that our type upholds the guarantees that -it can be safely sent across threads or accessed from multiple threads; -therefore, we need to do those checks manually and indicate as such with -`unsafe`. +As an example, recall the `Send` and `Sync` marker traits we discussed in the +[“Extensible Concurrency with `Send` and `Sync`”][send-and-sync] +section in Chapter 16: The compiler implements these traits automatically if +our types are composed entirely of other types that implement `Send` and +`Sync`. If we implement a type that contains a type that does not implement +`Send` or `Sync`, such as raw pointers, and we want to mark that type as `Send` +or `Sync`, we must use `unsafe`. Rust can’t verify that our type upholds the +guarantees that it can be safely sent across threads or accessed from multiple +threads; therefore, we need to do those checks manually and indicate as such +with `unsafe`. ### Accessing Fields of a Union @@ -502,8 +502,8 @@ learn more about unions in [the Rust Reference][unions]. When writing unsafe code, you might want to check that what you have written actually is safe and correct. One of the best ways to do that is to use Miri, an official Rust tool for detecting undefined behavior. Whereas the borrow -checker is a _static_ tool which works at compile time, Miri is a _dynamic_ -tool which works at runtime. It checks your code by running your program, or +checker is a _static_ tool that works at compile time, Miri is a _dynamic_ +tool that works at runtime. It checks your code by running your program, or its test suite, and detecting when you violate the rules it understands about how Rust should work. @@ -523,12 +523,12 @@ against Listing 20-7. ``` Miri correctly warns us that we’re casting an integer to a pointer, which might -be a problem but Miri can’t detect if there is because it doesn’t know how the -pointer originated. Then, Miri returns an error where Listing 20-7 has -undefined behavior because we have a dangling pointer. Thanks to Miri, we now -know there is a risk of undefined behavior, and we can think about how to make -the code safe. In some cases, Miri can even make recommendations about how to -fix errors. +be a problem, but Miri can’t determine whether a problem exists because it +doesn’t know how the pointer originated. Then, Miri returns an error where +Listing 20-7 has undefined behavior because we have a dangling pointer. Thanks +to Miri, we now know there is a risk of undefined behavior, and we can think +about how to make the code safe. In some cases, Miri can even make +recommendations about how to fix errors. Miri doesn’t catch everything you might get wrong when writing unsafe code. Miri is a dynamic analysis tool, so it only catches problems with code that @@ -543,7 +543,11 @@ this chapter and see what it says! You can learn more about Miri at [its GitHub repository][miri]. -### When to Use Unsafe Code + + + + +### Using Unsafe Code Correctly Using `unsafe` to use one of the five superpowers just discussed isn’t wrong or even frowned upon, but it is trickier to get `unsafe` code correct because the @@ -554,12 +558,12 @@ write unsafe code, you can use Miri to help you be more confident that the code you have written upholds Rust’s rules. For a much deeper exploration of how to work effectively with unsafe Rust, read -Rust’s official guide to the subject, the [Rustonomicon][nomicon]. +Rust’s official guide for `unsafe`, [The Rustonomicon][nomicon]. [dangling-references]: ch04-02-references-and-borrowing.html#dangling-references [ABI]: ../reference/items/external-blocks.html#abi -[differences-between-variables-and-constants]: ch03-01-variables-and-mutability.html#constants -[extensible-concurrency-with-the-send-and-sync-traits]: ch16-04-extensible-concurrency-sync-and-send.html#extensible-concurrency-with-the-send-and-sync-traits +[constants]: ch03-01-variables-and-mutability.html#declaring-constants +[send-and-sync]: ch16-04-extensible-concurrency-sync-and-send.html [the-slice-type]: ch04-03-slices.html#the-slice-type [unions]: ../reference/items/unions.html [miri]: https://github.com/rust-lang/miri diff --git a/src/ch20-02-advanced-traits.md b/src/ch20-02-advanced-traits.md index ae94987e90..a16d5c8347 100644 --- a/src/ch20-02-advanced-traits.md +++ b/src/ch20-02-advanced-traits.md @@ -1,9 +1,9 @@ ## Advanced Traits -We first covered traits in [“Traits: Defining Shared -Behavior”][traits-defining-shared-behavior] in Chapter 10, but we -didn’t discuss the more advanced details. Now that you know more about Rust, we -can get into the nitty-gritty. +We first covered traits in the [“Defining Shared Behavior with +Traits”][traits] section in Chapter 10, but we didn’t discuss +the more advanced details. Now that you know more about Rust, we can get into +the nitty-gritty. @@ -20,7 +20,7 @@ trait that uses some types without needing to know exactly what those types are until the trait is implemented. We’ve described most of the advanced features in this chapter as being rarely -needed. Associated types are somewhere in the middle: they’re used more rarely +needed. Associated types are somewhere in the middle: They’re used more rarely than features explained in the rest of the book but more commonly than many of the other features discussed in this chapter. @@ -57,7 +57,7 @@ the `Item` type is `u32`: -This syntax seems comparable to that of generics. So why not just define the +This syntax seems comparable to that of generics. So, why not just define the `Iterator` trait with generics, as shown in Listing 20-14? @@ -77,14 +77,14 @@ the concrete types of the generic type parameters each time. When we use the `next` method on `Counter`, we would have to provide type annotations to indicate which implementation of `Iterator` we want to use. -With associated types, we don’t need to annotate types because we can’t +With associated types, we don’t need to annotate types, because we can’t implement a trait on a type multiple times. In Listing 20-13 with the definition that uses associated types, we can choose what the type of `Item` will be only once because there can be only one `impl Iterator for Counter`. We don’t have to specify that we want an iterator of `u32` values everywhere we call `next` on `Counter`. -Associated types also become part of the trait’s contract: implementors of the +Associated types also become part of the trait’s contract: Implementors of the trait must provide a type to stand in for the associated type placeholder. Associated types often have a name that describes how the type will be used, and documenting the associated type in the API documentation is a good practice. @@ -93,7 +93,7 @@ and documenting the associated type in the API documentation is a good practice. -### Using Default Generic Type Parameters and Operator Overloading +### Using Default Generic Parameters and Operator Overloading When we use generic type parameters, we can specify a default concrete type for the generic type. This eliminates the need for implementors of the trait to @@ -107,7 +107,7 @@ in particular situations. Rust doesn’t allow you to create your own operators or overload arbitrary operators. But you can overload the operations and corresponding traits listed in `std::ops` by implementing the traits associated with the operator. For -example, in Listing 20-15 we overload the `+` operator to add two `Point` +example, in Listing 20-15, we overload the `+` operator to add two `Point` instances together. We do this by implementing the `Add` trait on a `Point` struct. @@ -136,7 +136,7 @@ trait Add { ``` This code should look generally familiar: a trait with one method and an -associated type. The new part is `Rhs=Self`: this syntax is called _default +associated type. The new part is `Rhs=Self`: This syntax is called _default type parameters_. The `Rhs` generic type parameter (short for “right-hand side”) defines the type of the `rhs` parameter in the `add` method. If we don’t specify a concrete type for `Rhs` when we implement the `Add` trait, the type @@ -150,11 +150,11 @@ default. We have two structs, `Millimeters` and `Meters`, holding values in different units. This thin wrapping of an existing type in another struct is known as the -_newtype pattern_, which we describe in more detail in the [“Using the Newtype -Pattern to Implement External Traits”][newtype] section. We want to add values in millimeters to values in meters and have -the implementation of `Add` do the conversion correctly. We can implement `Add` -for `Millimeters` with `Meters` as the `Rhs`, as shown in Listing 20-16. +_newtype pattern_, which we describe in more detail in the [“Implementing +External Traits with the Newtype Pattern”][newtype] section. We +want to add values in millimeters to values in meters and have the +implementation of `Add` do the conversion correctly. We can implement `Add` for +`Millimeters` with `Meters` as the `Rhs`, as shown in Listing 20-16. @@ -173,13 +173,13 @@ You’ll use default type parameters in two main ways: 2. To allow customization in specific cases most users won’t need The standard library’s `Add` trait is an example of the second purpose: -usually, you’ll add two like types, but the `Add` trait provides the ability to +Usually, you’ll add two like types, but the `Add` trait provides the ability to customize beyond that. Using a default type parameter in the `Add` trait definition means you don’t have to specify the extra parameter most of the time. In other words, a bit of implementation boilerplate isn’t needed, making it easier to use the trait. -The first purpose is similar to the second but in reverse: if you want to add a +The first purpose is similar to the second but in reverse: If you want to add a type parameter to an existing trait, you can give it a default to allow extension of the functionality of the trait without breaking the existing implementation code. @@ -255,7 +255,7 @@ trait to use based on the type of `self`. However, associated functions that are not methods don’t have a `self` parameter. When there are multiple types or traits that define non-method functions with the same function name, Rust doesn’t always know which type you -mean unless you use fully qualified syntax. For example, in Listing 20-20 we +mean unless you use fully qualified syntax. For example, in Listing 20-20, we create a trait for an animal shelter that wants to name all baby dogs Spot. We make an `Animal` trait with an associated non-method function `baby_name`. The `Animal` trait is implemented for the struct `Dog`, on which we also provide an @@ -283,10 +283,10 @@ function defined on `Dog` directly. This code prints the following: ``` This output isn’t what we wanted. We want to call the `baby_name` function that -is part of the `Animal` trait that we implemented on `Dog` so the code prints -`A baby dog is called a puppy`. The technique of specifying the trait name that -we used in Listing 20-19 doesn’t help here; if we change `main` to the code in -Listing 20-21, we’ll get a compilation error. +is part of the `Animal` trait that we implemented on `Dog` so that the code +prints `A baby dog is called a puppy`. The technique of specifying the trait +name that we used in Listing 20-19 doesn’t help here; if we change `main` to +the code in Listing 20-21, we’ll get a compilation error. @@ -333,7 +333,7 @@ In general, fully qualified syntax is defined as follows: ``` For associated functions that aren’t methods, there would not be a `receiver`: -there would only be the list of other arguments. You could use fully qualified +There would only be the list of other arguments. You could use fully qualified syntax everywhere that you call functions or methods. However, you’re allowed to omit any part of this syntax that Rust can figure out from other information in the program. You only need to use this more verbose syntax in cases where @@ -346,7 +346,7 @@ to identify which implementation you want to call. ### Using Supertraits -Sometimes you might write a trait definition that depends on another trait: for +Sometimes you might write a trait definition that depends on another trait: For a type to implement the first trait, you want to require that type to also implement the second trait. You would do this so that your trait definition can make use of the associated items of the second trait. The trait your trait @@ -429,24 +429,23 @@ it within an outline of asterisks. ### Implementing External Traits with the Newtype Pattern -In [“Implementing a Trait on a Type”][implementing-a-trait-on-a-type] in Chapter 10, we mentioned the orphan rule that states we’re only -allowed to implement a trait on a type if either the trait or the type, or -both, are local to our crate. It’s possible to get around this restriction -using the _newtype pattern_, which involves creating a new type in a tuple -struct. (We covered tuple structs in [“Using Tuple Structs Without Named Fields -to Create Different Types”][tuple-structs] in Chapter 5.) The -tuple struct will have one field and be a thin wrapper around the type for -which we want to implement a trait. Then the wrapper type is local to our -crate, and we can implement the trait on the wrapper. _Newtype_ is a term that -originates from the Haskell programming language. There is no runtime -performance penalty for using this pattern, and the wrapper type is elided at -compile time. +In the [“Implementing a Trait on a Type”][implementing-a-trait-on-a-type] section in Chapter 10, we mentioned the orphan rule that states +we’re only allowed to implement a trait on a type if either the trait or the +type, or both, are local to our crate. It’s possible to get around this +restriction using the newtype pattern, which involves creating a new type in a +tuple struct. (We covered tuple structs in the [“Creating Different Types with +Tuple Structs”][tuple-structs] section in Chapter 5.) The tuple +struct will have one field and be a thin wrapper around the type for which we +want to implement a trait. Then, the wrapper type is local to our crate, and we +can implement the trait on the wrapper. _Newtype_ is a term that originates +from the Haskell programming language. There is no runtime performance penalty +for using this pattern, and the wrapper type is elided at compile time. As an example, let’s say we want to implement `Display` on `Vec`, which the orphan rule prevents us from doing directly because the `Display` trait and the `Vec` type are defined outside our crate. We can make a `Wrapper` struct -that holds an instance of `Vec`; then we can implement `Display` on +that holds an instance of `Vec`; then, we can implement `Display` on `Wrapper` and use the `Vec` value, as shown in Listing 20-24. @@ -459,7 +458,7 @@ that holds an instance of `Vec`; then we can implement `Display` on The implementation of `Display` uses `self.0` to access the inner `Vec` because `Wrapper` is a tuple struct and `Vec` is the item at index 0 in the -tuple. Then we can use the functionality of the `Display` trait on `Wrapper`. +tuple. Then, we can use the functionality of the `Display` trait on `Wrapper`. The downside of using this technique is that `Wrapper` is a new type, so it doesn’t have the methods of the value it’s holding. We would have to implement @@ -467,17 +466,17 @@ all the methods of `Vec` directly on `Wrapper` such that the methods delegate to `self.0`, which would allow us to treat `Wrapper` exactly like a `Vec`. If we wanted the new type to have every method the inner type has, implementing the `Deref` trait on the `Wrapper` to return the inner type would -be a solution (we discussed implementing the `Deref` trait in [“Treating Smart -Pointers Like Regular References with `Deref`”][smart-pointer-deref] in Chapter 15). If we didn’t want the `Wrapper` type to have all the +be a solution (we discussed implementing the `Deref` trait in the [“Treating +Smart Pointers Like Regular References”][smart-pointer-deref] +section in Chapter 15). If we didn’t want the `Wrapper` type to have all the methods of the inner type—for example, to restrict the `Wrapper` type’s behavior—we would have to implement just the methods we do want manually. This newtype pattern is also useful even when traits are not involved. Let’s switch focus and look at some advanced ways to interact with Rust’s type system. -[newtype]: ch20-02-advanced-traits.html#using-the-newtype-pattern-to-implement-external-traits +[newtype]: ch20-02-advanced-traits.html#implementing-external-traits-with-the-newtype-pattern [implementing-a-trait-on-a-type]: ch10-02-traits.html#implementing-a-trait-on-a-type -[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 -[tuple-structs]: ch05-01-defining-structs.html#using-tuple-structs-without-named-fields-to-create-different-types +[traits]: ch10-02-traits.html +[smart-pointer-deref]: ch15-02-deref.html#treating-smart-pointers-like-regular-references +[tuple-structs]: ch05-01-defining-structs.html#creating-different-types-with-tuple-structs diff --git a/src/ch20-03-advanced-types.md b/src/ch20-03-advanced-types.md index e3307d2583..02e418f36b 100644 --- a/src/ch20-03-advanced-types.md +++ b/src/ch20-03-advanced-types.md @@ -2,7 +2,7 @@ The Rust type system has some features that we’ve so far mentioned but haven’t yet discussed. We’ll start by discussing newtypes in general as we examine why -newtypes are useful as types. Then we’ll move on to type aliases, a feature +they are useful as types. Then, we’ll move on to type aliases, a feature similar to newtypes but with slightly different semantics. We’ll also discuss the `!` type and dynamically sized types. @@ -12,19 +12,18 @@ the `!` type and dynamically sized types. ### Type Safety and Abstraction with the Newtype Pattern -This section assumes you’ve read the earlier section [“Using the Newtype Pattern -to Implement External Traits”][using-the-newtype-pattern]. The newtype pattern is also useful for tasks beyond those we’ve -discussed so far, including statically enforcing that values are never confused -and indicating the units of a value. You saw an example of using newtypes to -indicate units in Listing 20-16: recall that the `Millimeters` and `Meters` -structs wrapped `u32` values in a newtype. If we wrote a function with a -parameter of type `Millimeters`, we wouldn’t be able to compile a program that -accidentally tried to call that function with a value of type `Meters` or a -plain `u32`. +This section assumes you’ve read the earlier section [“Implementing External +Traits with the Newtype Pattern”][newtype]. The newtype pattern +is also useful for tasks beyond those we’ve discussed so far, including +statically enforcing that values are never confused and indicating the units of +a value. You saw an example of using newtypes to indicate units in Listing +20-16: Recall that the `Millimeters` and `Meters` structs wrapped `u32` values +in a newtype. If we wrote a function with a parameter of type `Millimeters`, we +wouldn’t be able to compile a program that accidentally tried to call that +function with a value of type `Meters` or a plain `u32`. We can also use the newtype pattern to abstract away some implementation -details of a type: the new type can expose a public API that is different from +details of a type: The new type can expose a public API that is different from the API of the private inner type. Newtypes can also hide internal implementation. For example, we could provide a @@ -32,10 +31,11 @@ Newtypes can also hide internal implementation. For example, we could provide a associated with their name. Code using `People` would only interact with the public API we provide, such as a method to add a name string to the `People` collection; that code wouldn’t need to know that we assign an `i32` ID to names -internally. The newtype pattern is a lightweight way to achieve encapsulation to -hide implementation details, which we discussed in [“Encapsulation that Hides -Implementation Details”][encapsulation-that-hides-implementation-details] in Chapter 18. +internally. The newtype pattern is a lightweight way to achieve encapsulation +to hide implementation details, which we discussed in the [“Encapsulation that +Hides Implementation +Details”][encapsulation-that-hides-implementation-details] +section in Chapter 18. @@ -61,7 +61,7 @@ values of type `i32`: ``` Because `Kilometers` and `i32` are the same type, we can add values of both -types and we can pass `Kilometers` values to functions that take `i32` +types and can pass `Kilometers` values to functions that take `i32` parameters. However, using this method, we don’t get the type-checking benefits that we get from the newtype pattern discussed earlier. In other words, if we mix up `Kilometers` and `i32` values somewhere, the compiler will not give us @@ -75,7 +75,7 @@ Box ``` Writing this lengthy type in function signatures and as type annotations all -over the code can be tiresome and error prone. Imagine having a project full of +over the code can be tiresome and error-prone. Imagine having a project full of code like that in Listing 20-25. @@ -131,7 +131,7 @@ looking like this: {{#rustdoc_include ../listings/ch20-advanced-features/no-listing-06-result-alias/src/lib.rs:there}} ``` -The type alias helps in two ways: it makes code easier to write _and_ it gives +The type alias helps in two ways: It makes code easier to write _and_ it gives us a consistent interface across all of `std::io`. Because it’s an alias, it’s just another `Result`, which means we can use any methods that work on `Result` with it, as well as special syntax like the `?` operator. @@ -164,16 +164,16 @@ here in Listing 20-27. At the time, we skipped over some details in this code. In [“The `match` -Control Flow Construct”][the-match-control-flow-construct] in -Chapter 6, we discussed that `match` arms must all return the same type. So, -for example, the following code doesn’t work: +Control Flow Construct”][the-match-control-flow-construct] +section in Chapter 6, we discussed that `match` arms must all return the same +type. So, for example, the following code doesn’t work: ```rust,ignore,does_not_compile {{#rustdoc_include ../listings/ch20-advanced-features/no-listing-08-match-arms-different-types/src/main.rs:here}} ``` The type of `guess` in this code would have to be an integer _and_ a string, -and Rust requires that `guess` have only one type. So what does `continue` +and Rust requires that `guess` have only one type. So, what does `continue` return? How were we allowed to return a `u32` from one arm and have another arm that ends with `continue` in Listing 20-27? @@ -202,7 +202,7 @@ of the overall `match` expression is `T`. This code works because `panic!` doesn’t produce a value; it ends the program. In the `None` case, we won’t be returning a value from `unwrap`, so this code is valid. -One final expression that has the type `!` is a `loop`: +One final expression that has the type `!` is a loop: ```rust,ignore {{#rustdoc_include ../listings/ch20-advanced-features/no-listing-10-loop-returns-never/src/main.rs:here}} @@ -238,28 +238,28 @@ same amount of space. But they have different lengths: `s1` needs 12 bytes of storage and `s2` needs 15. This is why it’s not possible to create a variable holding a dynamically sized type. -So what do we do? In this case, you already know the answer: we make the types -of `s1` and `s2` a `&str` rather than a `str`. Recall from [“String -Slices”][string-slices] in Chapter 4 that the slice data -structure just stores the starting position and the length of the slice. So, -although a `&T` is a single value that stores the memory address of where the -`T` is located, a `&str` is _two_ values: the address of the `str` and its -length. As such, we can know the size of a `&str` value at compile time: it’s -twice the length of a `usize`. That is, we always know the size of a `&str`, no -matter how long the string it refers to is. In general, this is the way in which -dynamically sized types are used in Rust: they have an extra bit of metadata -that stores the size of the dynamic information. The golden rule of dynamically -sized types is that we must always put values of dynamically sized types behind -a pointer of some kind. +So, what do we do? In this case, you already know the answer: We make the type +of `s1` and `s2` string slice (`&str`) rather than `str`. Recall from the +[“String Slices”][string-slices] section in Chapter 4 that the +slice data structure only stores the starting position and the length of the +slice. So, although `&T` is a single value that stores the memory address of +where the `T` is located, a string slice is _two_ values: the address of the +`str` and its length. As such, we can know the size of a string slice value at +compile time: It’s twice the length of a `usize`. That is, we always know the +size of a string slice, no matter how long the string it refers to is. In +general, this is the way in which dynamically sized types are used in Rust: +They have an extra bit of metadata that stores the size of the dynamic +information. The golden rule of dynamically sized types is that we must always +put values of dynamically sized types behind a pointer of some kind. We can combine `str` with all kinds of pointers: for example, `Box` or `Rc`. In fact, you’ve seen this before but with a different dynamically sized type: traits. Every trait is a dynamically sized type we can refer to by -using the name of the trait. In [“Using Trait Objects to Abstract over Shared -Behavior”][using-trait-objects-to-abstract-over-shared-behavior] -in Chapter 18, we mentioned that to use traits as trait objects, we must put -them behind a pointer, such as `&dyn Trait` or `Box` (`Rc` would work too). +using the name of the trait. In the [“Using Trait Objects to Abstract over +Shared Behavior”][using-trait-objects-to-abstract-over-shared-behavior] section in Chapter 18, we mentioned that to use traits as trait +objects, we must put them behind a pointer, such as `&dyn Trait` or `Box` (`Rc` would work too). To work with DSTs, Rust provides the `Sized` trait to determine whether or not a type’s size is known at compile time. This trait is automatically implemented @@ -285,7 +285,7 @@ restriction: {{#rustdoc_include ../listings/ch20-advanced-features/no-listing-14-generic-maybe-sized/src/lib.rs}} ``` -A trait bound on `?Sized` means “`T` may or may not be `Sized`” and this +A trait bound on `?Sized` means “`T` may or may not be `Sized`,” and this notation overrides the default that generic types must have a known size at compile time. The `?Trait` syntax with this meaning is only available for `Sized`, not any other traits. @@ -300,4 +300,4 @@ Next, we’ll talk about functions and closures! [string-slices]: ch04-03-slices.html#string-slices [the-match-control-flow-construct]: ch06-02-match.html#the-match-control-flow-construct [using-trait-objects-to-abstract-over-shared-behavior]: ch18-02-trait-objects.html#using-trait-objects-to-abstract-over-shared-behavior -[using-the-newtype-pattern]: ch20-02-advanced-traits.html#using-the-newtype-pattern-to-implement-external-traits +[newtype]: ch20-02-advanced-traits.html#implementing-external-traits-with-the-newtype-pattern diff --git a/src/ch20-04-advanced-functions-and-closures.md b/src/ch20-04-advanced-functions-and-closures.md index 8c625d1f78..72aa6a25b2 100644 --- a/src/ch20-04-advanced-functions-and-closures.md +++ b/src/ch20-04-advanced-functions-and-closures.md @@ -42,7 +42,7 @@ of the `Fn` traits as a trait bound. Function pointers implement all three of the closure traits (`Fn`, `FnMut`, and `FnOnce`), meaning you can always pass a function pointer as an argument for a function that expects a closure. It’s best to write functions using a generic -type and one of the closure traits so your functions can accept either +type and one of the closure traits so that your functions can accept either functions or closures. That said, one example of where you would want to only accept `fn` and not @@ -65,7 +65,7 @@ numbers into a vector of strings, we could use a closure, as in Listing 20-29. Or we could name a function as the argument to `map` instead of the closure. Listing 20-30 shows what this would look like. -+ ```rust {{#rustdoc_include ../listings/ch20-advanced-features/listing-20-30/src/main.rs:here}} @@ -73,19 +73,19 @@ Listing 20-30 shows what this would look like. -Note that we must use the fully qualified syntax that we talked about in -[“Advanced Traits”][advanced-traits] because there are multiple -functions available named `to_string`. +Note that we must use the fully qualified syntax that we talked about in the +[“Advanced Traits”][advanced-traits] section because there are +multiple functions available named `to_string`. Here, we’re using the `to_string` function defined in the `ToString` trait, which the standard library has implemented for any type that implements `Display`. -Recall from [“Enum Values”][enum-values] in Chapter 6 that the -name of each enum variant that we define also becomes an initializer function. -We can use these initializer functions as function pointers that implement the -closure traits, which means we can specify the initializer functions as -arguments for methods that take closures, as seen in Listing 20-31. +Recall from the [“Enum Values”][enum-values] section in Chapter +6 that the name of each enum variant that we define also becomes an initializer +function. We can use these initializer functions as function pointers that +implement the closure traits, which means we can specify the initializer +functions as arguments for methods that take closures, as seen in Listing 20-31. @@ -111,7 +111,7 @@ pointer `fn` as a return type if the closure captures any values from its scope, for example. Instead, you will normally use the `impl Trait` syntax we learned about in -Chapter 10. You can return any function type, using `Fn`, `FnOnce` and `FnMut`. +Chapter 10. You can return any function type, using `Fn`, `FnOnce`, and `FnMut`. For example, the code in Listing 20-32 will compile just fine. @@ -122,12 +122,12 @@ For example, the code in Listing 20-32 will compile just fine. -However, as we noted in [“Closure Type Inference and -Annotation”][closure-types] in Chapter 13, each closure is also -its own distinct type. If you need to work with multiple functions that have the -same signature but different implementations, you will need to use a trait -object for them. Consider what happens if you write code like that shown in -Listing 20-33. +However, as we noted in the [“Inferring and Annotating Closure +Types”][closure-types] section in Chapter 13, each closure is +also its own distinct type. If you need to work with multiple functions that +have the same signature but different implementations, you will need to use a +trait object for them. Consider what happens if you write code like that shown +in Listing 20-33. @@ -149,15 +149,15 @@ compile this, Rust lets us know that it won’t work: The error message tells us that whenever we return an `impl Trait`, Rust creates a unique _opaque type_, a type where we cannot see into the details of what Rust constructs for us, nor can we guess the type Rust will generate to -write ourselves. So even though these functions return closures that implement +write ourselves. So, even though these functions return closures that implement the same trait, `Fn(i32) -> i32`, the opaque types Rust generates for each are distinct. (This is similar to how Rust produces different concrete types for distinct async blocks even when they have the same output type, as we saw in -[“Working with Any Number of Futures”][any-number-of-futures] in -Chapter 17.) We have seen a solution to this problem a few times now: we can +[“The `Pin` Type and the `Unpin` Trait”][future-types] in +Chapter 17.) We have seen a solution to this problem a few times now: We can use a trait object, as in Listing 20-34. -+ ```rust {{#rustdoc_include ../listings/ch20-advanced-features/listing-20-34/src/main.rs:here}} @@ -166,14 +166,13 @@ use a trait object, as in Listing 20-34. This code will compile just fine. For more about trait objects, refer to the -section [“Using Trait Objects That Allow for Values of Different -Types”][using-trait-objects-to-abstract-over-shared-behavior] in Chapter 18. +section [“Using Trait Objects To Abstract over Shared +Behavior”][trait-objects] in Chapter 18. Next, let’s look at macros! [advanced-traits]: ch20-02-advanced-traits.html#advanced-traits [enum-values]: ch06-01-defining-an-enum.html#enum-values [closure-types]: ch13-01-closures.html#closure-type-inference-and-annotation -[any-number-of-futures]: ch17-03-more-futures.html -[using-trait-objects-to-abstract-over-shared-behavior]: ch18-02-trait-objects.html#using-trait-objects-to-abstract-over-shared-behavior +[future-types]: ch17-03-more-futures.html +[trait-objects]: ch18-02-trait-objects.html diff --git a/src/ch20-05-macros.md b/src/ch20-05-macros.md index ddf6b2b434..6d2e48f5a2 100644 --- a/src/ch20-05-macros.md +++ b/src/ch20-05-macros.md @@ -2,8 +2,8 @@ We’ve used macros like `println!` throughout this book, but we haven’t fully explored what a macro is and how it works. The term _macro_ refers to a family -of features in Rust: _declarative_ macros with `macro_rules!` and three kinds -of _procedural_ macros: +of features in Rust—declarative macros with `macro_rules!` and three kinds of +procedural macros: - Custom `#[derive]` macros that specify code added with the `derive` attribute used on structs and enums @@ -28,7 +28,7 @@ some additional powers that functions don’t have. A function signature must declare the number and type of parameters the function has. Macros, on the other hand, can take a variable number of -parameters: we can call `println!("hello")` with one argument or +parameters: We can call `println!("hello")` with one argument or `println!("hello {}", name)` with two arguments. Also, macros are expanded before the compiler interprets the meaning of the code, so a macro can, for example, implement a trait on a given type. A function can’t, because it gets @@ -57,7 +57,7 @@ something similar to a Rust `match` expression. As discussed in Chapter 6, `match` expressions are control structures that take an expression, compare the resultant value of the expression to patterns, and then run the code associated with the matching pattern. Macros also compare a value to patterns that are -associated with particular code: in this situation, the value is the literal +associated with particular code: In this situation, the value is the literal Rust source code passed to the macro; the patterns are compared with the structure of that source code; and the code associated with each pattern, when matched, replaces the code passed to the macro. This all happens during @@ -113,7 +113,7 @@ structure rather than values. Let’s walk through what the pattern pieces in Listing 20-29 mean; for the full macro pattern syntax, see the [Rust Reference][ref]. -First we use a set of parentheses to encompass the whole pattern. We use a +First, we use a set of parentheses to encompass the whole pattern. We use a dollar sign (`$`) to declare a variable in the macro system that will contain the Rust code matching the pattern. The dollar sign makes it clear this is a macro variable as opposed to a regular Rust variable. Next comes a set of @@ -171,7 +171,7 @@ macro variety. ```rust,ignore -use proc_macro; +use proc_macro::TokenStream; #[some_attribute] pub fn some_name(input: TokenStream) -> TokenStream { @@ -183,7 +183,7 @@ pub fn some_name(input: TokenStream) -> TokenStream { The function that defines a procedural macro takes a `TokenStream` as an input and produces a `TokenStream` as an output. The `TokenStream` type is defined by the `proc_macro` crate that is included with Rust and represents a sequence of -tokens. This is the core of the macro: the source code that the macro is +tokens. This is the core of the macro: The source code that the macro is operating on makes up the input `TokenStream`, and the code the macro produces is the output `TokenStream`. The function also has an attribute attached to it that specifies which kind of procedural macro we’re creating. We can have @@ -202,7 +202,7 @@ other forms different. Let’s create a crate named `hello_macro` that defines a trait named `HelloMacro` with one associated function named `hello_macro`. Rather than making our users implement the `HelloMacro` trait for each of their types, -we’ll provide a procedural macro so users can annotate their type with +we’ll provide a procedural macro so that users can annotate their type with `#[derive(HelloMacro)]` to get a default implementation of the `hello_macro` function. The default implementation will print `Hello, Macro! My name is TypeName!` where `TypeName` is the name of the type on which this trait has @@ -258,7 +258,7 @@ name at runtime. We need a macro to generate code at compile time. The next step is to define the procedural macro. At the time of this writing, procedural macros need to be in their own crate. Eventually, this restriction might be lifted. The convention for structuring crates and macro crates is as -follows: for a crate named `foo`, a custom `derive` procedural macro crate is +follows: For a crate named `foo`, a custom `derive` procedural macro crate is called `foo_derive`. Let’s start a new crate called `hello_macro_derive` inside our `hello_macro` project: @@ -304,14 +304,14 @@ won’t compile until we add a definition for the `impl_hello_macro` function. Notice that we’ve split the code into the `hello_macro_derive` function, which is responsible for parsing the `TokenStream`, and the `impl_hello_macro` -function, which is responsible for transforming the syntax tree: this makes +function, which is responsible for transforming the syntax tree: This makes writing a procedural macro more convenient. The code in the outer function (`hello_macro_derive` in this case) will be the same for almost every procedural macro crate you see or create. The code you specify in the body of the inner function (`impl_hello_macro` in this case) will be different depending on your procedural macro’s purpose. -We’ve introduced three new crates: `proc_macro`, [`syn`][syn], +We’ve introduced three new crates: `proc_macro`, [`syn`][syn], and [`quote`][quote]. The `proc_macro` crate comes with Rust, so we didn’t need to add that to the dependencies in _Cargo.toml_. The `proc_macro` crate is the compiler’s API that allows us to read and manipulate @@ -320,7 +320,7 @@ Rust code from our code. The `syn` crate parses Rust code from a string into a data structure that we can perform operations on. The `quote` crate turns `syn` data structures back into Rust code. These crates make it much simpler to parse any sort of Rust -code we might want to handle: writing a full parser for Rust code is no simple +code we might want to handle: Writing a full parser for Rust code is no simple task. The `hello_macro_derive` function will be called when a user of our library @@ -395,18 +395,18 @@ into a `DeriveInput` instance, let’s generate the code that implements the We get an `Ident` struct instance containing the name (identifier) of the annotated type using `ast.ident`. The struct in Listing 20-41 shows that when we run the `impl_hello_macro` function on the code in Listing 20-37, the -`ident` we get will have the `ident` field with a value of `"Pancakes"`. Thus +`ident` we get will have the `ident` field with a value of `"Pancakes"`. Thus, the `name` variable in Listing 20-42 will contain an `Ident` struct instance that, when printed, will be the string `"Pancakes"`, the name of the struct in Listing 20-37. The `quote!` macro lets us define the Rust code that we want to return. The -compiler expects something different to the direct result of the `quote!` +compiler expects something different from the direct result of the `quote!` macro’s execution, so we need to convert it to a `TokenStream`. We do this by calling the `into` method, which consumes this intermediate representation and returns a value of the required `TokenStream` type. -The `quote!` macro also provides some very cool templating mechanics: we can +The `quote!` macro also provides some very cool templating mechanics: We can enter `#name`, and `quote!` will replace it with the value in the variable `name`. You can even do some repetition similar to the way regular macros work. Check out [the `quote` crate’s docs][quote-docs] for a thorough introduction. @@ -420,10 +420,11 @@ the name of the annotated type. The `stringify!` macro used here is built into Rust. It takes a Rust expression, such as `1 + 2`, and at compile time turns the expression into a string literal, such as `"1 + 2"`. This is different from `format!` or -`println!`, macros which evaluate the expression and then turn the result into -a `String`. There is a possibility that the `#name` input might be an -expression to print literally, so we use `stringify!`. Using `stringify!` also -saves an allocation by converting `#name` to a string literal at compile time. +`println!`, which are macros that evaluate the expression and then turn the +result into a `String`. There is a possibility that the `#name` input might be +an expression to print literally, so we use `stringify!`. Using `stringify!` +also saves an allocation by converting `#name` to a string literal at compile +time. At this point, `cargo build` should complete successfully in both `hello_macro` and `hello_macro_derive`. Let’s hook up these crates to the code in Listing @@ -439,8 +440,8 @@ dependencies as follows: {{#include ../listings/ch20-advanced-features/no-listing-21-pancakes/pancakes/Cargo.toml:6:8}} ``` -Put the code in Listing 20-37 into _src/main.rs_, and run `cargo run`: it -should print `Hello, Macro! My name is Pancakes!` The implementation of the +Put the code in Listing 20-37 into _src/main.rs_, and run `cargo run`: It +should print `Hello, Macro! My name is Pancakes!`. The implementation of the `HelloMacro` trait from the procedural macro was included without the `pancakes` crate needing to implement it; the `#[derive(HelloMacro)]` added the trait implementation. @@ -476,16 +477,16 @@ item the attribute is attached to: in this case, `fn index() {}` and the rest of the function’s body. Other than that, attribute-like macros work the same way as custom `derive` -macros: you create a crate with the `proc-macro` crate type and implement a +macros: You create a crate with the `proc-macro` crate type and implement a function that generates the code you want! ### Function-Like Macros Function-like macros define macros that look like function calls. Similarly to `macro_rules!` macros, they’re more flexible than functions; for example, they -can take an unknown number of arguments. However, `macro_rules!` macros can only -be defined using the match-like syntax we discussed in [“Declarative Macros with -`macro_rules!` for General Metaprogramming”][decl] earlier. +can take an unknown number of arguments. However, `macro_rules!` macros can +only be defined using the match-like syntax we discussed in the [“Declarative +Macros for General Metaprogramming”][decl] section earlier. Function-like macros take a `TokenStream` parameter, and their definition manipulates that `TokenStream` using Rust code as the other two types of procedural macros do. An example of a function-like macro is an `sql!` macro @@ -504,7 +505,7 @@ syntactically correct, which is much more complex processing than a pub fn sql(input: TokenStream) -> TokenStream { ``` -This definition is similar to the custom `derive` macro’s signature: we receive +This definition is similar to the custom `derive` macro’s signature: We receive the tokens that are inside the parentheses and return the code we wanted to generate. diff --git a/src/ch21-00-final-project-a-web-server.md b/src/ch21-00-final-project-a-web-server.md index 0da101fc4c..32cede1cc1 100644 --- a/src/ch21-00-final-project-a-web-server.md +++ b/src/ch21-00-final-project-a-web-server.md @@ -5,7 +5,7 @@ chapter, we’ll build one more project together to demonstrate some of the concepts we covered in the final chapters, as well as recap some earlier lessons. -For our final project, we’ll make a web server that says “hello” and looks like +For our final project, we’ll make a web server that says “Hello!” and looks like Figure 21-1 in a web browser. Here is our plan for building the web server: @@ -16,18 +16,18 @@ Here is our plan for building the web server: 4. Create a proper HTTP response. 5. Improve the throughput of our server with a thread pool. -![hello from rust](img/trpl21-01.png) +Screenshot of a web browser visiting the address 127.0.0.1:8080 displaying a webpage with the text content “Hello! Hi from Rust” Figure 21-1: Our final shared project Before we get started, we should mention two details. First, the method we’ll use won’t be the best way to build a web server with Rust. Community members have published a number of production-ready crates available at -[crates.io](https://crates.io/) that provide more complete web server and thread -pool implementations than we’ll build. However, our intention in this chapter is -to help you learn, not to take the easy route. Because Rust is a systems -programming language, we can choose the level of abstraction we want to work -with and can go to a lower level than is possible or practical in other +[crates.io](https://crates.io/) that provide more complete web server and +thread pool implementations than we’ll build. However, our intention in this +chapter is to help you learn, not to take the easy route. Because Rust is a +systems programming language, we can choose the level of abstraction we want to +work with and can go to a lower level than is possible or practical in other languages. Second, we will not be using async and await here. Building a thread pool is a @@ -36,6 +36,6 @@ However, we will note how async and await might be applicable to some of the same problems we will see in this chapter. Ultimately, as we noted back in Chapter 17, many async runtimes use thread pools for managing their work. -We’ll therefore write the basic HTTP server and thread pool manually so you can -learn the general ideas and techniques behind the crates you might use in the -future. +We’ll therefore write the basic HTTP server and thread pool manually so that +you can learn the general ideas and techniques behind the crates you might use +in the future. diff --git a/src/ch21-01-single-threaded.md b/src/ch21-01-single-threaded.md index 1f26b94995..fabac99ca4 100644 --- a/src/ch21-01-single-threaded.md +++ b/src/ch21-01-single-threaded.md @@ -56,7 +56,7 @@ because, in networking, connecting to a port to listen to is known as “binding to a port.” The `bind` function returns a `Result`, which indicates that it’s -possible for binding to fail. For example, if we ran two instances of our +possible for binding to fail, for example, if we ran two instances of our program and so had two programs listening to the same port. Because we’re writing a basic server just for learning purposes, we won’t worry about handling these kinds of errors; instead, we use `unwrap` to stop the program if @@ -64,8 +64,8 @@ errors happen. The `incoming` method on `TcpListener` returns an iterator that gives us a sequence of streams (more specifically, streams of type `TcpStream`). A single -_stream_ represents an open connection between the client and the server. A -_connection_ is the name for the full request and response process in which a +_stream_ represents an open connection between the client and the server. +_Connection_ is the name for the full request and response process in which a client connects to the server, the server generates a response, and the server closes the connection. As such, we will read from the `TcpStream` to see what the client sent and then write our response to the stream to send data back to @@ -109,8 +109,8 @@ part of the `drop` implementation. Browsers sometimes deal with closed connections by retrying, because the problem might be temporary. Browsers also sometimes open multiple connections to the server without sending -any requests, so that if they *do* later send requests, those requests can -happen faster. When this happens, our server will see each connection, +any requests so that if they *do* later send requests, those requests can +happen more quickly. When this occurs, our server will see each connection, regardless of whether there are any requests over that connection. Many versions of Chrome-based browsers do this, for example; you can disable that optimization by using private browsing mode or using a different browser. @@ -119,7 +119,7 @@ The important factor is that we’ve successfully gotten a handle to a TCP connection! Remember to stop the program by pressing ctrl-C when -you’re done running a particular version of the code. Then restart the program +you’re done running a particular version of the code. Then, restart the program by invoking the `cargo run` command after you’ve made each set of code changes to make sure you’re running the newest code. @@ -129,8 +129,8 @@ Let’s implement the functionality to read the request from the browser! To separate the concerns of first getting a connection and then taking some action with the connection, we’ll start a new function for processing connections. In this new `handle_connection` function, we’ll read data from the TCP stream and -print it so we can see the data being sent from the browser. Change the code to -look like Listing 21-2. +print it so that we can see the data being sent from the browser. Change the +code to look like Listing 21-2. @@ -140,7 +140,7 @@ look like Listing 21-2. -We bring `std::io::prelude` and `std::io::BufReader` into scope to get access +We bring `std::io::BufReader` and `std::io::prelude` into scope to get access to traits and types that let us read from and write to the stream. In the `for` loop in the `main` function, instead of printing a message that says we made a connection, we now call the new `handle_connection` function and pass the @@ -157,7 +157,7 @@ lines in a vector by adding the `Vec<_>` type annotation. `BufReader` implements the `std::io::BufRead` trait, which provides the `lines` method. The `lines` method returns an iterator of `Result` by splitting the stream of data whenever it sees a newline -byte. To get each `String`, we map and `unwrap` each `Result`. The `Result` +byte. To get each `String`, we `map` and `unwrap` each `Result`. The `Result` might be an error if the data isn’t valid UTF-8 or if there was a problem reading from the stream. Again, a production program should handle these errors more gracefully, but we’re choosing to stop the program in the error case for @@ -166,17 +166,24 @@ simplicity. The browser signals the end of an HTTP request by sending two newline characters in a row, so to get one request from the stream, we take lines until we get a line that is the empty string. Once we’ve collected the lines into the -vector, we’re printing them out using pretty debug formatting so we can take a -look at the instructions the web browser is sending to our server. +vector, we’re printing them out using pretty debug formatting so that we can +take a look at the instructions the web browser is sending to our server. Let’s try this code! Start the program and make a request in a web browser again. Note that we’ll still get an error page in the browser, but our program’s output in the terminal will now look similar to this: + + ```console $ cargo run Compiling hello v0.1.0 (file:///projects/hello) - Finished dev [unoptimized + debuginfo] target(s) in 0.42s + Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.42s Running `target/debug/hello` Request: [ "GET / HTTP/1.1", @@ -209,8 +216,9 @@ our program. + -### Looking Closer at an HTTP Request +### Looking More Closely at an HTTP Request HTTP is a text-based protocol, and a request takes this format: @@ -221,19 +229,19 @@ message-body ``` The first line is the _request line_ that holds information about what the -client is requesting. The first part of the request line indicates the _method_ +client is requesting. The first part of the request line indicates the method being used, such as `GET` or `POST`, which describes how the client is making this request. Our client used a `GET` request, which means it is asking for information. The next part of the request line is _/_, which indicates the _uniform resource -identifier_ _(URI)_ the client is requesting: a URI is almost, but not quite, +identifier_ _(URI)_ the client is requesting: A URI is almost, but not quite, the same as a _uniform resource locator_ _(URL)_. The difference between URIs and URLs isn’t important for our purposes in this chapter, but the HTTP spec uses the term _URI_, so we can just mentally substitute _URL_ for _URI_ here. The last part is the HTTP version the client uses, and then the request line -ends in a CRLF sequence. (CRLF stands for _carriage return_ and _line feed_, +ends in a CRLF sequence. (_CRLF_ stands for _carriage return_ and _line feed_, which are terms from the typewriter days!) The CRLF sequence can also be written as `\r\n`, where `\r` is a carriage return and `\n` is a line feed. The _CRLF sequence_ separates the request line from the rest of the request data. @@ -268,7 +276,7 @@ a reason phrase that provides a text description of the status code. After the CRLF sequence are any headers, another CRLF sequence, and the body of the response. -Here is an example response that uses HTTP version 1.1, and has a status code of +Here is an example response that uses HTTP version 1.1 and has a status code of 200, an OK reason phrase, no headers, and no body: ```text @@ -290,11 +298,11 @@ Listing 21-3. The first new line defines the `response` variable that holds the success -message’s data. Then we call `as_bytes` on our `response` to convert the string -data to bytes. The `write_all` method on `stream` takes a `&[u8]` and sends -those bytes directly down the connection. Because the `write_all` operation -could fail, we use `unwrap` on any error result as before. Again, in a real -application you would add error handling here. +message’s data. Then, we call `as_bytes` on our `response` to convert the +string data to bytes. The `write_all` method on `stream` takes a `&[u8]` and +sends those bytes directly down the connection. Because the `write_all` +operation could fail, we use `unwrap` on any error result as before. Again, in +a real application, you would add error handling here. With these changes, let’s run our code and make a request. We’re no longer printing any data to the terminal, so we won’t see any output other than the @@ -336,8 +344,8 @@ string should look familiar; we used it when we read the contents of a file for our I/O project in Listing 12-4. Next, we use `format!` to add the file’s contents as the body of the success -response. To ensure a valid HTTP response, we add the `Content-Length` header -which is set to the size of our response body, in this case the size of +response. To ensure a valid HTTP response, we add the `Content-Length` header, +which is set to the size of our response body—in this case, the size of `hello.html`. Run this code with `cargo run` and load _127.0.0.1:7878_ in your browser; you @@ -355,7 +363,7 @@ request to _/_. Right now, our web server will return the HTML in the file no matter what the client requested. Let’s add functionality to check that the browser is -requesting _/_ before returning the HTML file, and return an error if the +requesting _/_ before returning the HTML file and to return an error if the browser requests anything else. For this we need to modify `handle_connection`, as shown in Listing 21-6. This new code checks the content of the request received against what we know a request for _/_ looks like and adds `if` and @@ -405,7 +413,7 @@ indicating the response to the end user. Here, our response has a status line with status code 404 and the reason phrase `NOT FOUND`. The body of the response will be the HTML in the file _404.html_. You’ll need to create a _404.html_ file next to _hello.html_ for the error -page; again feel free to use any HTML you want, or use the example HTML in +page; again, feel free to use any HTML you want, or use the example HTML in Listing 21-8. @@ -426,14 +434,14 @@ _127.0.0.1:7878/foo_, should return the error HTML from _404.html_. ### Refactoring -At the moment, the `if` and `else` blocks have a lot of repetition: they’re both -reading files and writing the contents of the files to the stream. The only -differences are the status line and the filename. Let’s make the code more +At the moment, the `if` and `else` blocks have a lot of repetition: They’re +both reading files and writing the contents of the files to the stream. The +only differences are the status line and the filename. Let’s make the code more concise by pulling out those differences into separate `if` and `else` lines -that will assign the values of the status line and the filename to variables; we -can then use those variables unconditionally in the code to read the file and -write the response. Listing 21-9 shows the resultant code after replacing the -large `if` and `else` blocks. +that will assign the values of the status line and the filename to variables; +we can then use those variables unconditionally in the code to read the file +and write the response. Listing 21-9 shows the resultant code after replacing +the large `if` and `else` blocks. @@ -461,5 +469,5 @@ requests with a 404 response. Currently, our server runs in a single thread, meaning it can only serve one request at a time. Let’s examine how that can be a problem by simulating some -slow requests. Then we’ll fix it so our server can handle multiple requests at -once. +slow requests. Then, we’ll fix it so that our server can handle multiple +requests at once. diff --git a/src/ch21-02-multithreaded.md b/src/ch21-02-multithreaded.md index 97ceeea8bd..c13249556d 100644 --- a/src/ch21-02-multithreaded.md +++ b/src/ch21-02-multithreaded.md @@ -1,23 +1,25 @@ + -## From Single-Threaded to Multithreaded Server +## From a Single-Threaded to a Multithreaded Server Right now, the server will process each request in turn, meaning it won’t -process a second connection until the first is finished processing. If the -server received more and more requests, this serial execution would be less and -less optimal. If the server receives a request that takes a long time to -process, subsequent requests will have to wait until the long request is +process a second connection until the first connection is finished processing. +If the server received more and more requests, this serial execution would be +less and less optimal. If the server receives a request that takes a long time +to process, subsequent requests will have to wait until the long request is finished, even if the new requests can be processed quickly. We’ll need to fix this, but first we’ll look at the problem in action. + ### Simulating a Slow Request -We’ll look at how a slow-processing request can affect other requests made to +We’ll look at how a slowly processing request can affect other requests made to our current server implementation. Listing 21-10 implements handling a request to _/sleep_ with a simulated slow response that will cause the server to sleep for five seconds before responding. @@ -40,14 +42,14 @@ matches a request to _/sleep_. When that request is received, the server will sleep for five seconds before rendering the successful HTML page. The third arm is the same as the `else` block from Listing 21-9. -You can see how primitive our server is: real libraries would handle the +You can see how primitive our server is: Real libraries would handle the recognition of multiple requests in a much less verbose way! -Start the server using `cargo run`. Then open two browser windows: one for -_http://127.0.0.1:7878_ and the other for _http://127.0.0.1:7878/sleep_. If -you enter the _/_ URI a few times, as before, you’ll see it respond quickly. -But if you enter _/sleep_ and then load _/_, you’ll see that _/_ waits until -`sleep` has slept for its full five seconds before loading. +Start the server using `cargo run`. Then, open two browser windows: one for +_http://127.0.0.1:7878_ and the other for _http://127.0.0.1:7878/sleep_. If you +enter the _/_ URI a few times, as before, you’ll see it respond quickly. But if +you enter _/sleep_ and then load _/_, you’ll see that _/_ waits until `sleep` +has slept for its full five seconds before loading. There are multiple techniques we could use to avoid requests backing up behind a slow request, including using async as we did Chapter 17; the one we’ll @@ -55,7 +57,7 @@ implement is a thread pool. ### Improving Throughput with a Thread Pool -A _thread pool_ is a group of spawned threads that are waiting and ready to +A _thread pool_ is a group of spawned threads that are ready and waiting to handle a task. When the program receives a new task, it assigns one of the threads in the pool to the task, and that thread will process the task. The remaining threads in the pool are available to handle any other tasks that come @@ -66,7 +68,7 @@ increasing the throughput of your server. We’ll limit the number of threads in the pool to a small number to protect us from DoS attacks; if we had our program create a new thread for each request as -it came in, someone making 10 million requests to our server could create havoc +it came in, someone making 10 million requests to our server could wreak havoc by using up all our server’s resources and grinding the processing of requests to a halt. @@ -89,10 +91,10 @@ options are possible. Before we begin implementing a thread pool, let’s talk about what using the pool should look like. When you’re trying to design code, writing the client -interface first can help guide your design. Write the API of the code so it’s -structured in the way you want to call it; then implement the functionality -within that structure rather than implementing the functionality and then -designing the public API. +interface first can help guide your design. Write the API of the code so that +it’s structured in the way you want to call it; then, implement the +functionality within that structure rather than implementing the functionality +and then designing the public API. Similar to how we used test-driven development in the project in Chapter 12, we’ll use compiler-driven development here. We’ll write the code that calls the @@ -109,7 +111,7 @@ we’ll explore the technique we’re not going to use as a starting point. First, let’s explore how our code might look if it did create a new thread for every connection. As mentioned earlier, this isn’t our final plan due to the problems with potentially spawning an unlimited number of threads, but it is a -starting point to get a working multithreaded server first. Then we’ll add the +starting point to get a working multithreaded server first. Then, we’ll add the thread pool as an improvement, and contrasting the two solutions will be easier. Listing 21-11 shows the changes to make to `main` to spawn a new thread to @@ -155,10 +157,10 @@ struct we want to use instead of `thread::spawn`. We use `ThreadPool::new` to create a new thread pool with a configurable number of threads, in this case four. Then, in the `for` loop, `pool.execute` has a -similar interface as `thread::spawn` in that it takes a closure the pool should -run for each stream. We need to implement `pool.execute` so it takes the -closure and gives it to a thread in the pool to run. This code won’t yet -compile, but we’ll try so that the compiler can guide us in how to fix it. +similar interface as `thread::spawn` in that it takes a closure that the pool +should run for each stream. We need to implement `pool.execute` so that it +takes the closure and gives it to a thread in the pool to run. This code won’t +yet compile, but we’ll try so that the compiler can guide us in how to fix it. @@ -176,7 +178,7 @@ error we get: Great! This error tells us we need a `ThreadPool` type or module, so we’ll build one now. Our `ThreadPool` implementation will be independent of the kind -of work our web server is doing. So let’s switch the `hello` crate from a +of work our web server is doing. So, let’s switch the `hello` crate from a binary crate to a library crate to hold our `ThreadPool` implementation. After we change to a library crate, we could also use the separate thread pool library for any work we want to do using a thread pool, not just for serving @@ -194,7 +196,7 @@ definition of a `ThreadPool` struct that we can have for now: -Then edit the _main.rs_ file to bring `ThreadPool` into scope from the library +Then, edit the _main.rs_ file to bring `ThreadPool` into scope from the library crate by adding the following code to the top of _src/main.rs_: @@ -229,8 +231,8 @@ characteristics: We chose `usize` as the type of the `size` parameter because we know that a negative number of threads doesn’t make any sense. We also know we’ll use this `4` as the number of elements in a collection of threads, which is what the -`usize` type is for, as discussed in [“Integer Types”][integer-types] in Chapter 3. +`usize` type is for, as discussed in the [“Integer Types”][integer-types] section in Chapter 3. Let’s check the code again: @@ -239,20 +241,20 @@ Let’s check the code again: ``` Now the error occurs because we don’t have an `execute` method on `ThreadPool`. -Recall from [“Creating a Finite Number of -Threads”](#creating-a-finite-number-of-threads) that we decided -our thread pool should have an interface similar to `thread::spawn`. In -addition, we’ll implement the `execute` function so it takes the closure it’s -given and gives it to an idle thread in the pool to run. +Recall from the [“Creating a Finite Number of +Threads”](#creating-a-finite-number-of-threads) section that we +decided our thread pool should have an interface similar to `thread::spawn`. In +addition, we’ll implement the `execute` function so that 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 [“Moving Captured Values Out of the Closure and the `Fn` -Traits”][fn-traits] 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 +Closures”][moving-out-of-closures] 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 @@ -271,7 +273,7 @@ want to use because the thread for running a request will only execute that request’s closure one time, which matches the `Once` in `FnOnce`. The `F` type parameter also has the trait bound `Send` and the lifetime bound -`'static`, which are useful in our situation: we need `Send` to transfer the +`'static`, which are useful in our situation: We need `Send` to transfer the closure from one thread to another and `'static` because we don’t know how long the thread will take to execute. Let’s create an `execute` method on `ThreadPool` that will take a generic parameter of type `F` with these bounds: @@ -289,7 +291,7 @@ that takes no parameters and returns the unit type `()`. Just like function definitions, the return type can be omitted from the signature, but even if we have no parameters, we still need the parentheses. -Again, this is the simplest implementation of the `execute` method: it does +Again, this is the simplest implementation of the `execute` method: It does nothing, but we’re only trying to make our code compile. Let’s check it again: ```console @@ -302,13 +304,13 @@ the chapter. Our library isn’t actually calling the closure passed to `execute yet! > Note: A saying you might hear about languages with strict compilers, such as -> Haskell and Rust, is “if the code compiles, it works.” But this saying is not +> Haskell and Rust, is “If the code compiles, it works.” But this saying is not > universally true. Our project compiles, but it does absolutely nothing! If we > were building a real, complete project, this would be a good time to start > writing unit tests to check that the code compiles _and_ has the behavior we > want. -Consider: what would be different here if we were going to execute a future +Consider: What would be different here if we were going to execute a future instead of a closure? #### Validating the Number of Threads in `new` @@ -319,8 +321,8 @@ let’s think about `new`. Earlier we chose an unsigned type for the `size` parameter because a pool with a negative number of threads makes no sense. However, a pool with zero threads also makes no sense, yet zero is a perfectly valid `usize`. We’ll add code to check that `size` is greater than zero before -we return a `ThreadPool` instance and have the program panic if it receives a -zero by using the `assert!` macro, as shown in Listing 21-13. +we return a `ThreadPool` instance, and we’ll have the program panic if it +receives a zero by using the `assert!` macro, as shown in Listing 21-13. @@ -367,7 +369,7 @@ closure returns. Let’s try using `JoinHandle` too and see what happens. In our case, the closures we’re passing to the thread pool will handle the connection and not return anything, so `T` will be the unit type `()`. -The code in Listing 21-14 will compile but doesn’t create any threads yet. +The code in Listing 21-14 will compile, but it doesn’t create any threads yet. We’ve changed the definition of `ThreadPool` to hold a vector of `thread::JoinHandle<()>` instances, initialized the vector with a capacity of `size`, set up a `for` loop that will run some code to create the threads, and @@ -387,7 +389,7 @@ using `thread::JoinHandle` as the type of the items in the vector in Once a valid size is received, our `ThreadPool` creates a new vector that can hold `size` items. The `with_capacity` function performs the same task as -`Vec::new` but with an important difference: it pre-allocates space in the +`Vec::new` but with an important difference: It pre-allocates space in the vector. Because we know we need to store `size` elements in the vector, doing this allocation up front is slightly more efficient than using `Vec::new`, which resizes itself as elements are inserted. @@ -414,16 +416,17 @@ this data structure _Worker_, which is a common term in pooling implementations. The `Worker` picks up code that needs to be run and runs the code in its thread. -Think of people working in the kitchen at a restaurant: the workers wait until +Think of people working in the kitchen at a restaurant: The workers wait until orders come in from customers, and then they’re responsible for taking those orders and filling them. Instead of storing a vector of `JoinHandle<()>` instances in the thread pool, we’ll store instances of the `Worker` struct. Each `Worker` will store a single -`JoinHandle<()>` instance. Then we’ll implement a method on `Worker` that will +`JoinHandle<()>` instance. Then, we’ll implement a method on `Worker` that will take a closure of code to run and send it to the already running thread for -execution. We’ll also give each `Worker` an `id` so we can distinguish between -the different instances of `Worker` in the pool when logging or debugging. +execution. We’ll also give each `Worker` an `id` so that we can distinguish +between the different instances of `Worker` in the pool when logging or +debugging. Here is the new process that will happen when we create a `ThreadPool`. We’ll implement the code that sends the closure to the thread after we have `Worker` @@ -526,7 +529,7 @@ closure. The code in Listing 21-17 won’t quite compile yet. -We’ve made some small and straightforward changes: we pass the receiver into +We’ve made some small and straightforward changes: We pass the receiver into `Worker::new`, and then we use it inside the closure. When we try to check this code, we get this error: @@ -536,7 +539,7 @@ When we try to check this code, we get this error: ``` The code is trying to pass `receiver` to multiple `Worker` instances. This -won’t work, as you’ll recall from Chapter 16: the channel implementation that +won’t work, as you’ll recall from Chapter 16: The channel implementation that Rust provides is multiple _producer_, single _consumer_. This means we can’t just clone the consuming end of the channel to fix this code. We also don’t want to send a message multiple times to multiple consumers; we want one list @@ -547,7 +550,7 @@ Additionally, taking a job off the channel queue involves mutating the `receiver`, so the threads need a safe way to share and modify `receiver`; otherwise, we might get race conditions (as covered in Chapter 16). -Recall the thread-safe smart pointers discussed in Chapter 16: to share +Recall the thread-safe smart pointers discussed in Chapter 16: To share ownership across multiple threads and allow the threads to mutate the value, we need to use `Arc>`. The `Arc` type will let multiple `Worker` instances own the receiver, and `Mutex` will ensure that only one `Worker` gets a job from @@ -562,8 +565,8 @@ the receiver at a time. Listing 21-18 shows the changes we need to make. In `ThreadPool::new`, we put the receiver in an `Arc` and a `Mutex`. For each -new `Worker`, we clone the `Arc` to bump the reference count so the `Worker` -instances can share ownership of the receiver. +new `Worker`, we clone the `Arc` to bump the reference count so that the +`Worker` instances can share ownership of the receiver. With these changes, the code compiles! We’re getting there! @@ -571,10 +574,9 @@ With these changes, the code compiles! We’re getting there! Let’s finally implement the `execute` method on `ThreadPool`. We’ll also change `Job` from a struct to a type alias for a trait object that holds the type of -closure that `execute` receives. As discussed in [“Creating Type Synonyms with -Type Aliases”][creating-type-synonyms-with-type-aliases] in -Chapter 20, type aliases allow us to make long types shorter for ease of use. -Look at Listing 21-19. +closure that `execute` receives. As discussed in the [“Type Synonyms and Type +Aliases”][type-aliases] section in Chapter 20, type aliases +allow us to make long types shorter for ease of use. Look at Listing 21-19. @@ -589,7 +591,7 @@ send that job down the sending end of the channel. We’re calling `unwrap` on `send` for the case that sending fails. This might happen if, for example, we stop all our threads from executing, meaning the receiving end has stopped receiving new messages. At the moment, we can’t stop our threads from -executing: our threads continue executing as long as the pool exists. The +executing: Our threads continue executing as long as the pool exists. The reason we use `unwrap` is that we know the failure case won’t happen, but the compiler doesn’t know that. @@ -701,8 +703,8 @@ Listing 21-21. This code compiles and runs but doesn’t result in the desired threading -behavior: a slow request will still cause other requests to wait to be -processed. The reason is somewhat subtle: the `Mutex` struct has no public +behavior: A slow request will still cause other requests to wait to be +processed. The reason is somewhat subtle: The `Mutex` struct has no public `unlock` method because the ownership of the lock is based on the lifetime of the `MutexGuard` within the `LockResult>` that the `lock` method returns. At compile time, the borrow checker can then enforce the rule @@ -719,8 +721,8 @@ let` (and `if let` and `match`) does not drop temporary values until the end of the associated block. In Listing 21-21, the lock remains held for the duration of the call to `job()`, meaning other `Worker` instances cannot receive jobs. -[creating-type-synonyms-with-type-aliases]: ch20-03-advanced-types.html#creating-type-synonyms-with-type-aliases +[type-aliases]: ch20-03-advanced-types.html#type-synonyms-and-type-aliases [integer-types]: ch03-02-data-types.html#integer-types -[fn-traits]: ch13-01-closures.html#moving-captured-values-out-of-the-closure-and-the-fn-traits +[moving-out-of-closures]: ch13-01-closures.html#moving-captured-values-out-of-closures [builder]: ../std/thread/struct.Builder.html [builder-spawn]: ../std/thread/struct.Builder.html#method.spawn diff --git a/src/ch21-03-graceful-shutdown-and-cleanup.md b/src/ch21-03-graceful-shutdown-and-cleanup.md index aee7cca3ac..cfce45283b 100644 --- a/src/ch21-03-graceful-shutdown-and-cleanup.md +++ b/src/ch21-03-graceful-shutdown-and-cleanup.md @@ -9,15 +9,15 @@ are stopped immediately as well, even if they’re in the middle of serving a request. Next, then, we’ll implement the `Drop` trait to call `join` on each of the -threads in the pool so they can finish the requests they’re working on before -closing. Then we’ll implement a way to tell the threads they should stop -accepting new requests and shut down. To see this code in action, we’ll modify -our server to accept only two requests before gracefully shutting down its -thread pool. +threads in the pool so that they can finish the requests they’re working on +before closing. Then, we’ll implement a way to tell the threads they should +stop accepting new requests and shut down. To see this code in action, we’ll +modify our server to accept only two requests before gracefully shutting down +its thread pool. -One thing to notice as we go: none of this affects the parts of the code that -handle executing the closures, so everything here would be just the same if we -were using a thread pool for an async runtime. +One thing to notice as we go: None of this affects the parts of the code that +handle executing the closures, so everything here would be the same if we were +using a thread pool for an async runtime. ### Implementing the `Drop` Trait on `ThreadPool` @@ -34,7 +34,7 @@ quite work yet. -First we loop through each of the thread pool `workers`. We use `&mut` for this +First, we loop through each of the thread pool `workers`. We use `&mut` for this because `self` is a mutable reference, and we also need to be able to mutate `worker`. For each `worker`, we print a message saying that this particular `Worker` instance is shutting down, and then we call `join` on that `Worker` @@ -47,30 +47,31 @@ Here is the error we get when we compile this code: {{#include ../listings/ch21-web-server/listing-21-22/output.txt}} ``` -The error tells us we can’t call `join` because we only have a mutable borrow of -each `worker` and `join` takes ownership of its argument. To solve this issue, -we need to move the thread out of the `Worker` instance that owns `thread` so -`join` can consume the thread. One way to do this is by taking the same approach -we did in Listing 18-15. If `Worker` held an `Option>`, -we could call the `take` method on the `Option` to move the value out of the -`Some` variant and leave a `None` variant in its place. In other words, a -`Worker` that is running would have a `Some` variant in `thread`, and when we -wanted to clean up a `Worker`, we’d replace `Some` with `None` so the `Worker` -wouldn’t have a thread to run. - -However, the _only_ time this would come up would be when dropping the `Worker`. -In exchange, we’d have to deal with an `Option>` anywhere -we accessed `worker.thread`. Idiomatic Rust uses `Option` quite a bit, but when -you find yourself wrapping something you know will always be present in an -`Option` as a workaround like this, it’s a good idea to look for alternative -approaches to make your code cleaner and less error-prone. +The error tells us we can’t call `join` because we only have a mutable borrow +of each `worker` and `join` takes ownership of its argument. To solve this +issue, we need to move the thread out of the `Worker` instance that owns +`thread` so that `join` can consume the thread. One way to do this is to take +the same approach we took in Listing 18-15. If `Worker` held an +`Option>`, we could call the `take` method on the +`Option` to move the value out of the `Some` variant and leave a `None` variant +in its place. In other words, a `Worker` that is running would have a `Some` +variant in `thread`, and when we wanted to clean up a `Worker`, we’d replace +`Some` with `None` so that the `Worker` wouldn’t have a thread to run. + +However, the _only_ time this would come up would be when dropping the +`Worker`. In exchange, we’d have to deal with an +`Option>` anywhere we accessed `worker.thread`. +Idiomatic Rust uses `Option` quite a bit, but when you find yourself wrapping +something you know will always be present in an `Option` as a workaround like +this, it’s a good idea to look for alternative approaches to make your code +cleaner and less error-prone. In this case, a better alternative exists: the `Vec::drain` method. It accepts a range parameter to specify which items to remove from the vector and returns an iterator of those items. Passing the `..` range syntax will remove *every* value from the vector. -So we need to update the `ThreadPool` `drop` implementation like this: +So, we need to update the `ThreadPool` `drop` implementation like this: @@ -84,14 +85,14 @@ This resolves the compiler error and does not require any other changes to our code. Note that, because drop can be called when panicking, the unwrap could also panic and cause a double panic, which immediately crashes the program and ends any cleanup in progress. This is fine for an example program, -but isn’t recommended for production code. +but it isn’t recommended for production code. ### Signaling to the Threads to Stop Listening for Jobs With all the changes we’ve made, our code compiles without any warnings. However, the bad news is that this code doesn’t function the way we want it to yet. The key is the logic in the closures run by the threads of the `Worker` -instances: at the moment, we call `join`, but that won’t shut down the threads, +instances: At the moment, we call `join`, but that won’t shut down the threads, because they `loop` forever looking for jobs. If we try to drop our `ThreadPool` with our current implementation of `drop`, the main thread will block forever, waiting for the first thread to finish. @@ -99,7 +100,7 @@ block forever, waiting for the first thread to finish. To fix this problem, we’ll need a change in the `ThreadPool` `drop` implementation and then a change in the `Worker` loop. -First we’ll change the `ThreadPool` `drop` implementation to explicitly drop +First, we’ll change the `ThreadPool` `drop` implementation to explicitly drop the `sender` before waiting for the threads to finish. Listing 21-23 shows the changes to `ThreadPool` to explicitly drop `sender`. Unlike with the thread, here we _do_ need to use an `Option` to be able to move `sender` out of @@ -146,8 +147,8 @@ The `take` method is defined in the `Iterator` trait and limits the iteration to the first two items at most. The `ThreadPool` will go out of scope at the end of `main`, and the `drop` implementation will run. -Start the server with `cargo run`, and make three requests. The third request -should error, and in your terminal you should see output similar to this: +Start the server with `cargo run` and make three requests. The third request +should error, and in your terminal, you should see output similar to this: