diff --git a/listings/ch09-error-handling/listing-09-13/src/lib.rs b/listings/ch09-error-handling/listing-09-13/src/lib.rs new file mode 100644 index 0000000000..d093831100 --- /dev/null +++ b/listings/ch09-error-handling/listing-09-13/src/lib.rs @@ -0,0 +1,17 @@ +pub struct Guess { + value: i32, +} + +impl Guess { + pub fn new(value: i32) -> Guess { + if value < 1 || value > 100 { + panic!("Guess value must be between 1 and 100, got {value}."); + } + + Guess { value } + } + + pub fn value(&self) -> i32 { + self.value + } +} diff --git a/listings/ch09-error-handling/listing-09-13/src/main.rs b/listings/ch09-error-handling/listing-09-13/src/main.rs index 42b7d32ef6..cda389303b 100644 --- a/listings/ch09-error-handling/listing-09-13/src/main.rs +++ b/listings/ch09-error-handling/listing-09-13/src/main.rs @@ -1,27 +1,8 @@ +use guessing_game::Guess; use rand::Rng; use std::cmp::Ordering; use std::io; -// ANCHOR: here -pub struct Guess { - value: i32, -} - -impl Guess { - pub fn new(value: i32) -> Guess { - if value < 1 || value > 100 { - panic!("Guess value must be between 1 and 100, got {value}."); - } - - Guess { value } - } - - pub fn value(&self) -> i32 { - self.value - } -} -// ANCHOR_END: here - fn main() { println!("Guess the number!"); diff --git a/nostarch/chapter09.md b/nostarch/chapter09.md index 4aa81123fb..a7188076c7 100644 --- a/nostarch/chapter09.md +++ b/nostarch/chapter09.md @@ -31,7 +31,7 @@ about returning `Result` values. Additionally, we’ll explore considerations when deciding whether to try to recover from an error or to stop execution. -## Unrecoverable Errors with panic! +## Unrecoverable Errors with `panic!` Sometimes bad things happen in your code, and there’s nothing you can do about it. In these cases, Rust has the `panic!` macro. There are two ways to cause a @@ -45,20 +45,20 @@ panic occurs to make it easier to track down the source of the panic. > ### Unwinding the Stack or Aborting in Response to a Panic > > By default, when a panic occurs the program starts *unwinding*, which means -Rust walks back up the stack and cleans up the data from each function it -encounters. However, walking back and cleaning up is a lot of work. Rust, -therefore, allows you to choose the alternative of immediately *aborting*, -which ends the program without cleaning up. +> Rust walks back up the stack and cleans up the data from each function it +> encounters. However, walking back and cleaning up is a lot of work. Rust, +> therefore, allows you to choose the alternative of immediately *aborting*, +> which ends the program without cleaning up. > > Memory that the program was using will then need to be cleaned up by the -operating system. If in your project you need to make the resultant binary as -small as possible, you can switch from unwinding to aborting upon a panic by -adding `panic = 'abort'` to the appropriate `[profile]` sections in your -*Cargo.toml* file. For example, if you want to abort on panic in release mode, -add this: +> operating system. If in your project you need to make the resultant binary as +> small as possible, you can switch from unwinding to aborting upon a panic by +> adding `panic = 'abort'` to the appropriate `[profile]` sections in your +> *Cargo.toml* file. For example, if you want to abort on panic in release mode, +> add this: > -> ``` -> [profile.release] +> ```toml +> profile.release > panic = 'abort' > ``` @@ -75,9 +75,13 @@ fn main() { When you run the program, you’ll see something like this: ``` -thread 'main' panicked at 'crash and burn', src/main.rs:2:5 -note: run with `RUST_BACKTRACE=1` environment variable to display -a backtrace +$ cargo run + Compiling panic v0.1.0 (file:///projects/panic) + Finished dev [unoptimized + debuginfo] target(s) in 0.25s + Running `target/debug/panic` +thread 'main' panicked at src/main.rs:2:5: +crash and burn +note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace ``` The call to `panic!` causes the error message contained in the last two lines. @@ -91,6 +95,9 @@ be in code that our code calls, and the filename and line number reported by the error message will be someone else’s code where the `panic!` macro is called, not the line of our code that eventually led to the `panic!` call. + + + We can use the backtrace of the functions the `panic!` call came from to figure out the part of our code that is causing the problem. To understand how to use a `panic!` backtrace, let’s look at another example and see what it’s like when @@ -108,8 +115,8 @@ fn main() { } ``` -Listing 9-1: Attempting to access an element beyond the end of a vector, which -will cause a call to `panic!` +Listing 9-1: Attempting to access an element beyond the +end of a vector, which will cause a call to `panic!` Here, we’re attempting to access the 100th element of our vector (which is at index 99 because indexing starts at zero), but the vector has only three @@ -130,12 +137,17 @@ element at an index that doesn’t exist, Rust will stop execution and refuse to continue. Let’s try it and see: ``` -thread 'main' panicked at 'index out of bounds: the len is 3 but the index is -99', src/main.rs:4:5 +$ cargo run + Compiling panic v0.1.0 (file:///projects/panic) + Finished dev [unoptimized + debuginfo] target(s) in 0.27s + Running `target/debug/panic` +thread 'main' panicked at src/main.rs:4:6: +index out of bounds: the len is 3 but the index is 99 note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace ``` -This error points at line 4 of our *main.rs* where we attempt to access `index`. +This error points at line 4 of our *main.rs* where we attempt to access index +`99` of the vector in `v`. The `note:` line tells us that we can set the `RUST_BACKTRACE` environment variable to get a backtrace of exactly what happened to cause the error. A @@ -149,40 +161,39 @@ library code, or crates that you’re using. Let’s try getting a backtrace by setting the `RUST_BACKTRACE` environment variable to any value except `0`. Listing 9-2 shows output similar to what you’ll see. + + ``` $ RUST_BACKTRACE=1 cargo run -thread 'main' panicked at 'index out of bounds: the len is 3 but the index is -99', src/main.rs:4:5 +thread 'main' panicked at src/main.rs:4:6: +index out of bounds: the len is 3 but the index is 99 stack backtrace: 0: rust_begin_unwind - at /rustc/e092d0b6b43f2de967af0887873151bb1c0b18d3/library/std -/src/panicking.rs:584:5 + at /rustc/07dca489ac2d933c78d3c5158e3f43beefeb02ce/library/std/src/panicking.rs:645:5 1: core::panicking::panic_fmt - at /rustc/e092d0b6b43f2de967af0887873151bb1c0b18d3/library/core -/src/panicking.rs:142:14 + at /rustc/07dca489ac2d933c78d3c5158e3f43beefeb02ce/library/core/src/panicking.rs:72:14 2: core::panicking::panic_bounds_check - at /rustc/e092d0b6b43f2de967af0887873151bb1c0b18d3/library/core -/src/panicking.rs:84:5 + at /rustc/07dca489ac2d933c78d3c5158e3f43beefeb02ce/library/core/src/panicking.rs:208:5 3: >::index - at /rustc/e092d0b6b43f2de967af0887873151bb1c0b18d3/library/core -/src/slice/index.rs:242:10 + at /rustc/07dca489ac2d933c78d3c5158e3f43beefeb02ce/library/core/src/slice/index.rs:255:10 4: core::slice::index:: for [T]>::index - at /rustc/e092d0b6b43f2de967af0887873151bb1c0b18d3/library/core -/src/slice/index.rs:18:9 + at /rustc/07dca489ac2d933c78d3c5158e3f43beefeb02ce/library/core/src/slice/index.rs:18:9 5: as core::ops::index::Index>::index - at /rustc/e092d0b6b43f2de967af0887873151bb1c0b18d3/library/alloc -/src/vec/mod.rs:2591:9 + at /rustc/07dca489ac2d933c78d3c5158e3f43beefeb02ce/library/alloc/src/vec/mod.rs:2770:9 6: panic::main - at ./src/main.rs:4:5 + at ./src/main.rs:4:6 7: core::ops::function::FnOnce::call_once - at /rustc/e092d0b6b43f2de967af0887873151bb1c0b18d3/library/core -/src/ops/function.rs:248:5 -note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose -backtrace. + at /rustc/07dca489ac2d933c78d3c5158e3f43beefeb02ce/library/core/src/ops/function.rs:250:5 +note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace. ``` -Listing 9-2: The backtrace generated by a call to `panic!` displayed when the -environment variable `RUST_BACKTRACE` is set +Listing 9-2: The backtrace generated by a call to +`panic!` displayed when the environment variable `RUST_BACKTRACE` is set That’s a lot of output! The exact output you see might be different depending on your operating system and Rust version. In order to get backtraces with this @@ -200,10 +211,11 @@ panics in the future, you’ll need to figure out what action the code is taking with what values to cause the panic and what the code should do instead. We’ll come back to `panic!` and when we should and should not use `panic!` to -handle error conditions in “To panic! or Not to panic!” on page XX. Next, we’ll -look at how to recover from an error using `Result`. +handle error conditions in the “To `panic!` or Not to +`panic!`” section later in this +chapter. Next, we’ll look at how to recover from an error using `Result`. -## Recoverable Errors with Result +## Recoverable Errors with `Result` Most errors aren’t serious enough to require the program to stop entirely. Sometimes when a function fails it’s for a reason that you can easily interpret @@ -211,8 +223,8 @@ and respond to. For example, if you try to open a file and that operation fails because the file doesn’t exist, you might want to create the file instead of terminating the process. -Recall from “Handling Potential Failure with Result” on page XX that the -`Result` enum is defined as having two variants, `Ok` and `Err`, as follows: +Recall from “Handling Potential Failure with `Result`” in Chapter 2 that the `Result` enum is defined as having two +variants, `Ok` and `Err`, as follows: ``` enum Result { @@ -277,15 +289,13 @@ fn main() { let greeting_file = match greeting_file_result { Ok(file) => file, - Err(error) => { - panic!("Problem opening the file: {:?}", error); - } + Err(error) => panic!("Problem opening the file: {error:?}"), }; } ``` -Listing 9-4: Using a `match` expression to handle the `Result` variants that -might be returned +Listing 9-4: Using a `match` expression to handle the +`Result` variants that might be returned Note that, like the `Option` enum, the `Result` enum and its variants have been brought into scope by the prelude, so we don’t need to specify `Result::` @@ -302,9 +312,13 @@ there’s no file named *hello.txt* in our current directory and we run this code, we’ll see the following output from the `panic!` macro: ``` -thread 'main' panicked at 'Problem opening the file: Os { code: - 2, kind: NotFound, message: "No such file or directory" }', -src/main.rs:8:23 +$ cargo run + Compiling error-handling v0.1.0 (file:///projects/error-handling) + Finished dev [unoptimized + debuginfo] target(s) in 0.73s + Running `target/debug/error-handling` +thread 'main' panicked at src/main.rs:8:23: +Problem opening the file: Os { code: 2, kind: NotFound, message: "No such file or directory" } +note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace ``` As usual, this output tells us exactly what has gone wrong. @@ -321,6 +335,9 @@ add an inner `match` expression, shown in Listing 9-5. Filename: src/main.rs + + ``` use std::fs::File; use std::io::ErrorKind; @@ -331,27 +348,20 @@ fn main() { let greeting_file = match greeting_file_result { Ok(file) => file, Err(error) => match error.kind() { - ErrorKind::NotFound => { - match File::create("hello.txt") { - Ok(fc) => fc, - Err(e) => panic!( - "Problem creating the file: {:?}", - e - ), - } - } + ErrorKind::NotFound => match File::create("hello.txt") { + Ok(fc) => fc, + Err(e) => panic!("Problem creating the file: {e:?}"), + }, other_error => { - panic!( - "Problem opening the file: {:?}", - other_error - ); + panic!("Problem opening the file: {other_error:?}"); } }, }; } ``` -Listing 9-5: Handling different kinds of errors in different ways +Listing 9-5: Handling different kinds of errors in +different ways The type of the value that `File::open` returns inside the `Err` variant is `io::Error`, which is a struct provided by the standard library. This struct @@ -370,41 +380,42 @@ file can’t be created, a different error message is printed. The second arm of the outer `match` stays the same, so the program panics on any error besides the missing file error. -#### Alternatives to Using match with Result - -That’s a lot of `match`! The `match` expression is very useful but also very -much a primitive. In Chapter 13, you’ll learn about closures, which are used -with many of the methods defined on `Result`. These methods can be more -concise than using `match` when handling `Result` values in your code. - -For example, here’s another way to write the same logic as shown in Listing -9-5, this time using closures and the `unwrap_or_else` method: - -``` -// src/main.rs -use std::fs::File; -use std::io::ErrorKind; - -fn main() { - let greeting_file = File::open("hello.txt").unwrap_or_else(|error| { - if error.kind() == ErrorKind::NotFound { - File::create("hello.txt").unwrap_or_else(|error| { - panic!("Problem creating the file: {:?}", error); - }) - } else { - panic!("Problem opening the file: {:?}", error); - } - }); -} -``` - -Although this code has the same behavior as Listing 9-5, it doesn’t contain any -`match` expressions and is cleaner to read. Come back to this example after -you’ve read Chapter 13, and look up the `unwrap_or_else` method in the standard -library documentation. Many more of these methods can clean up huge nested -`match` expressions when you’re dealing with errors. +> #### Alternatives to Using `match` with `Result` +> +> That’s a lot of `match`! The `match` expression is very useful but also very +> much a primitive. In Chapter 13, you’ll learn about closures, which are used +> with many of the methods defined on `Result`. These methods can be more +> concise than using `match` when handling `Result` values in your code. +> +> For example, here’s another way to write the same logic as shown in Listing +> 9-5, this time using closures and the `unwrap_or_else` method: +> +> +> +> ```rust,ignore +> use std::fs::File; +> use std::io::ErrorKind; +> +> fn main() { +> let greeting_file = File::open("hello.txt").unwrap_or_else(|error| { +> if error.kind() == ErrorKind::NotFound { +> File::create("hello.txt").unwrap_or_else(|error| { +> panic!("Problem creating the file: {error:?}"); +> }) +> } else { +> panic!("Problem opening the file: {error:?}"); +> } +> }); +> } +> ``` +> +> Although this code has the same behavior as Listing 9-5, it doesn’t contain +> any `match` expressions and is cleaner to read. Come back to this example +> after you’ve read Chapter 13, and look up the `unwrap_or_else` method in the +> standard library documentation. Many more of these methods can clean up huge +> nested `match` expressions when you’re dealing with errors. -#### Shortcuts for Panic on Error: unwrap and expect +#### Shortcuts for Panic on Error: `unwrap` and `expect` Using `match` works well enough, but it can be a bit verbose and doesn’t always communicate intent well. The `Result` type has many helper methods @@ -427,10 +438,15 @@ fn main() { If we run this code without a *hello.txt* file, we’ll see an error message from the `panic!` call that the `unwrap` method makes: + + ``` -thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: Os { -code: 2, kind: NotFound, message: "No such file or directory" }', -src/main.rs:4:49 +thread 'main' panicked at src/main.rs:4:49: +called `Result::unwrap()` on an `Err` value: Os { code: 2, kind: NotFound, message: "No such file or directory" } ``` Similarly, the `expect` method lets us also choose the `panic!` error message. @@ -454,10 +470,15 @@ the `panic!` macro. The error message used by `expect` in its call to `panic!` will be the parameter that we pass to `expect`, rather than the default `panic!` message that `unwrap` uses. Here’s what it looks like: + + ``` -thread 'main' panicked at 'hello.txt should be included in this project: Os { -code: 2, kind: NotFound, message: "No such file or directory" }', -src/main.rs:5:10 +thread 'main' panicked at src/main.rs:5:10: +hello.txt should be included in this project: Os { code: 2, kind: NotFound, message: "No such file or directory" } ``` In production-quality code, most Rustaceans choose `expect` rather than @@ -480,67 +501,72 @@ to the code that called the function. Filename: src/main.rs + + ``` use std::fs::File; use std::io::{self, Read}; -1 fn read_username_from_file() -> Result { - 2 let username_file_result = File::open("hello.txt"); +fn read_username_from_file() -> Result { + let username_file_result = File::open("hello.txt"); - 3 let mut username_file = match username_file_result { - 4 Ok(file) => file, - 5 Err(e) => return Err(e), + let mut username_file = match username_file_result { + Ok(file) => file, + Err(e) => return Err(e), }; - 6 let mut username = String::new(); + let mut username = String::new(); - 7 match username_file.read_to_string(&mut username) { - 8 Ok(_) => Ok(username), - 9 Err(e) => Err(e), + match username_file.read_to_string(&mut username) { + Ok(_) => Ok(username), + Err(e) => Err(e), } } ``` -Listing 9-6: A function that returns errors to the calling code using `match` +Listing 9-6: A function that returns errors to the +calling code using `match` This function can be written in a much shorter way, but we’re going to start by doing a lot of it manually in order to explore error handling; at the end, we’ll show the shorter way. Let’s look at the return type of the function -first: `Result` [1]. This means the function is returning a +first: `Result`. This means the function is returning a value of the type `Result`, where the generic parameter `T` has been filled in with the concrete type `String` and the generic type `E` has been filled in with the concrete type `io::Error`. If this function succeeds without any problems, the code that calls this function will receive an `Ok` value that holds a `String`—the `username` that -this function read from the file [8]. If this function encounters any problems, -the calling code will receive an `Err` value that holds an instance of -`io::Error` that contains more information about what the problems were. We -chose `io::Error` as the return type of this function because that happens to -be the type of the error value returned from both of the operations we’re -calling in this function’s body that might fail: the `File::open` function [2] -and the `read_to_string` method [7]. - -The body of the function starts by calling the `File::open` function [2]. Then -we handle the `Result` value with a `match` similar to the `match` in Listing -9-4. If `File::open` succeeds, the file handle in the pattern variable `file` -[4] becomes the value in the mutable variable `username_file` [3] and the -function continues. In the `Err` case, instead of calling `panic!`, we use the -`return` keyword to return early out of the function entirely and pass the -error value from `File::open`, now in the pattern variable `e`, back to the -calling code as this function’s error value [5]. +this function read from the file. If this function encounters any problems, the +calling code will receive an `Err` value that holds an instance of `io::Error` +that contains more information about what the problems were. We chose +`io::Error` as the return type of this function because that happens to be the +type of the error value returned from both of the operations we’re calling in +this function’s body that might fail: the `File::open` function and the +`read_to_string` method. + +The body of the function starts by calling the `File::open` function. Then we +handle the `Result` value with a `match` similar to the `match` in Listing 9-4. +If `File::open` succeeds, the file handle in the pattern variable `file` +becomes the value in the mutable variable `username_file` and the function +continues. In the `Err` case, instead of calling `panic!`, we use the `return` +keyword to return early out of the function entirely and pass the error value +from `File::open`, now in the pattern variable `e`, back to the calling code as +this function’s error value. So, if we have a file handle in `username_file`, the function then creates a -new `String` in variable `username` [6] and calls the `read_to_string` method -on the file handle in `username_file` to read the contents of the file into -`username` [7]. The `read_to_string` method also returns a `Result` because it +new `String` in variable `username` and calls the `read_to_string` method on +the file handle in `username_file` to read the contents of the file into +`username`. The `read_to_string` method also returns a `Result` because it might fail, even though `File::open` succeeded. So we need another `match` to handle that `Result`: if `read_to_string` succeeds, then our function has succeeded, and we return the username from the file that’s now in `username` wrapped in an `Ok`. If `read_to_string` fails, we return the error value in the same way that we returned the error value in the `match` that handled the return value of `File::open`. However, we don’t need to explicitly say -`return`, because this is the last expression in the function [9]. +`return`, because this is the last expression in the function. The code that calls this code will then handle getting either an `Ok` value that contains a username or an `Err` value that contains an `io::Error`. It’s @@ -554,7 +580,7 @@ it to handle appropriately. This pattern of propagating errors is so common in Rust that Rust provides the question mark operator `?` to make this easier. -#### A Shortcut for Propagating Errors: The ? Operator +#### A Shortcut for Propagating Errors: the `?` Operator Listing 9-7 shows an implementation of `read_username_from_file` that has the same functionality as in Listing 9-6, but this implementation uses the `?` @@ -562,6 +588,10 @@ operator. Filename: src/main.rs + + ``` use std::fs::File; use std::io::{self, Read}; @@ -574,8 +604,8 @@ fn read_username_from_file() -> Result { } ``` -Listing 9-7: A function that returns errors to the calling code using the `?` -operator +Listing 9-7: A function that returns errors to the +calling code using the `?` operator The `?` placed after a `Result` value is defined to work in almost the same way as the `match` expressions we defined to handle the `Result` values in Listing @@ -614,6 +644,10 @@ method calls immediately after the `?`, as shown in Listing 9-8. Filename: src/main.rs + + ``` use std::fs::File; use std::io::{self, Read}; @@ -627,7 +661,8 @@ fn read_username_from_file() -> Result { } ``` -Listing 9-8: Chaining method calls after the `?` operator +Listing 9-8: Chaining method calls after the `?` +operator We’ve moved the creation of the new `String` in `username` to the beginning of the function; that part hasn’t changed. Instead of creating a variable @@ -642,6 +677,10 @@ Listing 9-9 shows a way to make this even shorter using `fs::read_to_string`. Filename: src/main.rs + + ``` use std::fs; use std::io; @@ -651,8 +690,8 @@ fn read_username_from_file() -> Result { } ``` -Listing 9-9: Using `fs::read_to_string` instead of opening and then reading the -file +Listing 9-9: Using `fs::read_to_string` instead of +opening and then reading the file Reading a file into a string is a fairly common operation, so the standard library provides the convenient `fs::read_to_string` function that opens the @@ -661,7 +700,7 @@ into that `String`, and returns it. Of course, using `fs::read_to_string` doesn’t give us the opportunity to explain all the error handling, so we did it the longer way first. -#### Where the ? Operator Can Be Used +#### Where The `?` Operator Can Be Used The `?` operator can only be used in functions whose return type is compatible with the value the `?` is used on. This is because the `?` operator is defined @@ -685,8 +724,8 @@ fn main() { } ``` -Listing 9-10: Attempting to use the `?` in the `main` function that returns -`()` won’t compile. +Listing 9-10: Attempting to use the `?` in the `main` +function that returns `()` won’t compile. This code opens a file, which might fail. The `?` operator follows the `Result` value returned by `File::open`, but this `main` function has the return type of @@ -694,19 +733,20 @@ value returned by `File::open`, but this `main` function has the return type of message: ``` -error[E0277]: the `?` operator can only be used in a function that returns -`Result` or `Option` (or another type that implements `FromResidual`) +$ cargo run + Compiling error-handling v0.1.0 (file:///projects/error-handling) +error[E0277]: the `?` operator can only be used in a function that returns `Result` or `Option` (or another type that implements `FromResidual`) --> src/main.rs:4:48 | -3 | / fn main() { -4 | | let greeting_file = File::open("hello.txt")?; - | | ^ cannot use the `?` -operator in a function that returns `()` -5 | | } - | |_- this function should return `Result` or `Option` to accept `?` +3 | fn main() { + | --------- this function should return `Result` or `Option` to accept `?` +4 | let greeting_file = File::open("hello.txt")?; + | ^ cannot use the `?` operator in a function that returns `()` | - = help: the trait `FromResidual>` is not -implemented for `()` + = help: the trait `FromResidual>` is not implemented for `()` + +For more information about this error, try `rustc --explain E0277`. +error: could not compile `error-handling` (bin "error-handling") due to 1 previous error ``` This error points out that we’re only allowed to use the `?` operator in a @@ -735,7 +775,8 @@ fn last_char_of_first_line(text: &str) -> Option { } ``` -Listing 9-11: Using the `?` operator on an `Option` value +Listing 9-11: Using the `?` operator on an `Option` +value This function returns `Option` because it’s possible that there is a character there, but it’s also possible that there isn’t. This code takes the @@ -788,18 +829,18 @@ fn main() -> Result<(), Box> { } ``` -Listing 9-12: Changing `main` to return `Result<(), E>` allows the use of the -`?` operator on `Result` values. +Listing 9-12: Changing `main` to return `Result<(), E>` +allows the use of the `?` operator on `Result` values. -The `Box` type is a *trait object*, which we’ll talk about in “Using -Trait Objects That Allow for Values of Different Types” on page XX. For now, -you can read `Box` to mean “any kind of error.” Using `?` on a -`Result` value in a `main` function with the error type `Box` is -allowed because it allows any `Err` value to be returned early. Even though the -body of this `main` function will only ever return errors of type -`std::io::Error`, by specifying `Box`, this signature will continue -to be correct even if more code that returns other errors is added to the body -of `main`. +The `Box` type is a *trait object*, which we’ll talk about in the +“Using Trait Objects that Allow for Values of Different +Types” section in Chapter 17. For now, you can +read `Box` to mean “any kind of error.” Using `?` on a `Result` +value in a `main` function with the error type `Box` is allowed +because it allows any `Err` value to be returned early. Even though the body of +this `main` function will only ever return errors of type `std::io::Error`, by +specifying `Box`, this signature will continue to be correct even if +more code that returns other errors is added to the body of `main`. When a `main` function returns a `Result<(), E>`, the executable will exit with a value of `0` if `main` returns `Ok(())` and will exit with a nonzero value if @@ -809,15 +850,16 @@ that error return some integer other than `0`. Rust also returns integers from executables to be compatible with this convention. The `main` function may return any types that implement the -`std::process::Termination` trait, which contains a function `report` that -returns an `ExitCode`. Consult the standard library documentation for more -information on implementing the `Termination` trait for your own types. +`std::process::Termination` trait, which contains +a function `report` that returns an `ExitCode`. Consult the standard library +documentation for more information on implementing the `Termination` trait for +your own types. Now that we’ve discussed the details of calling `panic!` or returning `Result`, let’s return to the topic of how to decide which is appropriate to use in which cases. -## To panic! or Not to panic! +## To `panic!` or Not to `panic!` So how do you decide when you should call `panic!` and when you should return `Result`? When code panics, there’s no way to recover. You could call `panic!` @@ -866,11 +908,11 @@ that you’ll never have an `Err` variant, it’s perfectly acceptable to call `Err` variant in the `expect` text. Here’s an example: ``` -use std::net::IpAddr; + use std::net::IpAddr; -let home: IpAddr = "127.0.0.1" - .parse() - .expect("Hardcoded IP address should be valid"); + let home: IpAddr = "127.0.0.1" + .parse() + .expect("Hardcoded IP address should be valid"); ``` We’re creating an `IpAddr` instance by parsing a hardcoded string. We can see @@ -895,12 +937,13 @@ contradictory values, or missing values are passed to your code—plus one or more of the following: * The bad state is something that is unexpected, as opposed to something that -will likely happen occasionally, like a user entering data in the wrong format. + will likely happen occasionally, like a user entering data in the wrong + format. * Your code after this point needs to rely on not being in this bad state, -rather than checking for the problem at every step. + rather than checking for the problem at every step. * There’s not a good way to encode this information in the types you use. We’ll -work through an example of what we mean in “Encoding States and Behavior as -Types” on page XX. + work through an example of what we mean in the “Encoding States and Behavior + as Types” section of Chapter 17. If someone calls your code and passes in values that don’t make sense, it’s best to return an error if you can so the user of the library can decide what @@ -965,22 +1008,22 @@ number being in range, like so: Filename: src/main.rs ``` -loop { - --snip-- + loop { + // --snip-- - let guess: i32 = match guess.trim().parse() { - Ok(num) => num, - Err(_) => continue, - }; + let guess: i32 = match guess.trim().parse() { + Ok(num) => num, + Err(_) => continue, + }; - if guess < 1 || guess > 100 { - println!("The secret number will be between 1 and 100."); - continue; - } + if guess < 1 || guess > 100 { + println!("The secret number will be between 1 and 100."); + continue; + } - match guess.cmp(&secret_number) { - --snip-- -} + match guess.cmp(&secret_number) { + // --snip-- + } ``` The `if` expression checks whether our value is out of range, tells the user @@ -1004,57 +1047,54 @@ receives a value between 1 and 100. Filename: src/lib.rs ``` -1 pub struct Guess { +pub struct Guess { value: i32, } impl Guess { - 2 pub fn new(value: i32) -> Guess { - 3 if value < 1 || value > 100 { - 4 panic!( - "Guess value must be between 1 and 100, got {}.", - value - ); + pub fn new(value: i32) -> Guess { + if value < 1 || value > 100 { + panic!("Guess value must be between 1 and 100, got {value}."); } - 5 Guess { value } + Guess { value } } - 6 pub fn value(&self) -> i32 { + pub fn value(&self) -> i32 { self.value } } ``` -Listing 9-13: A `Guess` type that will only continue with values between 1 and -100 +Listing 9-13: A `Guess` type that will only continue with +values between 1 and 100 First we define a struct named `Guess` that has a field named `value` that -holds an `i32` [1]. This is where the number will be stored. +holds an `i32`. This is where the number will be stored. Then we implement an associated function named `new` on `Guess` that creates -instances of `Guess` values [2]. The `new` function is defined to have one +instances of `Guess` values. The `new` function is defined to have one parameter named `value` of type `i32` and to return a `Guess`. The code in the -body of the `new` function tests `value` to make sure it’s between 1 and 100 -[3]. If `value` doesn’t pass this test, we make a `panic!` call [4], which will -alert the programmer who is writing the calling code that they have a bug they -need to fix, because creating a `Guess` with a `value` outside this range would +body of the `new` function tests `value` to make sure it’s between 1 and 100. +If `value` doesn’t pass this test, we make a `panic!` call, which will alert +the programmer who is writing the calling code that they have a bug they need +to fix, because creating a `Guess` with a `value` outside this range would violate the contract that `Guess::new` is relying on. The conditions in which `Guess::new` might panic should be discussed in its public-facing API documentation; we’ll cover documentation conventions indicating the possibility of a `panic!` in the API documentation that you create in Chapter 14. If `value` does pass the test, we create a new `Guess` with its `value` field set -to the `value` parameter and return the `Guess` [5]. +to the `value` parameter and return the `Guess`. Next, we implement a method named `value` that borrows `self`, doesn’t have any -other parameters, and returns an `i32` [6]. This kind of method is sometimes -called a *getter* because its purpose is to get some data from its fields and -return it. This public method is necessary because the `value` field of the -`Guess` struct is private. It’s important that the `value` field be private so -code using the `Guess` struct is not allowed to set `value` directly: code -outside the module *must* use the `Guess::new` function to create an instance -of `Guess`, thereby ensuring there’s no way for a `Guess` to have a `value` -that hasn’t been checked by the conditions in the `Guess::new` function. +other parameters, and returns an `i32`. This kind of method is sometimes called +a *getter* because its purpose is to get some data from its fields and return +it. This public method is necessary because the `value` field of the `Guess` +struct is private. It’s important that the `value` field be private so code +using the `Guess` struct is not allowed to set `value` directly: code outside +the module *must* use the `Guess::new` function to create an instance of +`Guess`, thereby ensuring there’s no way for a `Guess` to have a `value` that +hasn’t been checked by the conditions in the `Guess::new` function. A function that has a parameter or returns only numbers between 1 and 100 could then declare in its signature that it takes or returns a `Guess` rather than an @@ -1074,4 +1114,3 @@ situations will make your code more reliable in the face of inevitable problems. Now that you’ve seen useful ways that the standard library uses generics with the `Option` and `Result` enums, we’ll talk about how generics work and how you can use them in your code. - diff --git a/nostarch/chapter10.md b/nostarch/chapter10.md index bb8919d790..bc315d12ce 100644 --- a/nostarch/chapter10.md +++ b/nostarch/chapter10.md @@ -54,13 +54,13 @@ Filename: src/main.rs ``` fn main() { - 1 let number_list = vec![34, 50, 25, 100, 65]; + let number_list = vec![34, 50, 25, 100, 65]; - 2 let mut largest = &number_list[0]; + let mut largest = &number_list[0]; - 3 for number in &number_list { - 4 if number > largest { - 5 largest = number; + for number in &number_list { + if number > largest { + largest = number; } } @@ -68,16 +68,17 @@ fn main() { } ``` -Listing 10-1: Finding the largest number in a list of numbers +Listing 10-1: Finding the largest number in a list of +numbers -We store a list of integers in the variable `number_list` [1] and place a -reference to the first number in the list in a variable named `largest` [2]. We -then iterate through all the numbers in the list [3], and if the current number -is greater than the number stored in `largest` [4], we replace the reference in -that variable [5]. However, if the current number is less than or equal to the -largest number seen so far, the variable doesn’t change, and the code moves on -to the next number in the list. After considering all the numbers in the list, -`largest` should refer to the largest number, which in this case is 100. +We store a list of integers in the variable `number_list` and place a reference +to the first number in the list in a variable named `largest`. We then iterate +through all the numbers in the list, and if the current number is greater than +the number stored in `largest`, we replace the reference in that variable. +However, if the current number is less than or equal to the largest number seen +so far, the variable doesn’t change, and the code moves on to the next number +in the list. After considering all the numbers in the list, `largest` should +refer to the largest number, which in this case is 100. We’ve now been tasked with finding the largest number in two different lists of numbers. To do so, we can choose to duplicate the code in Listing 10-1 and use @@ -113,7 +114,8 @@ fn main() { } ``` -Listing 10-2: Code to find the largest number in *two* lists of numbers +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 have to remember to update the code in multiple places when we want to change @@ -157,18 +159,20 @@ fn main() { } ``` -Listing 10-3: Abstracted code to find the largest number in two lists +Listing 10-3: Abstracted code to find the largest number +in two lists The `largest` function has a parameter called `list`, which represents any concrete slice of `i32` values we might pass into the function. As a result, -when we call the function, the code runs on the specific values that we pass in. +when we call the function, the code runs on the specific values that we pass +in. In summary, here are the steps we took to change the code from Listing 10-2 to Listing 10-3: 1. Identify duplicate code. 1. Extract the duplicate code into the body of the function, and specify the -inputs and return values of that code in the function signature. + inputs and return values of that code in the function signature. 1. Update the two instances of duplicated code to call the function instead. Next, we’ll use these same steps with generics to reduce code duplication. In @@ -237,8 +241,8 @@ fn main() { } ``` -Listing 10-4: Two functions that differ only in their names and in the types in -their signatures +Listing 10-4: Two functions that differ only in their +names and in the types in their signatures The `largest_i32` function is the one we extracted in Listing 10-3 that finds the largest `i32` in a slice. The `largest_char` function finds the largest @@ -249,7 +253,7 @@ 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 +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 @@ -301,12 +305,14 @@ fn main() { } ``` -Listing 10-5: The `largest` function using generic type parameters; this -doesn’t compile yet +Listing 10-5: The `largest` function using generic type +parameters; this doesn’t compile yet If we compile this code right now, we’ll get this error: ``` +$ cargo run + Compiling chapter10 v0.1.0 (file:///projects/chapter10) error[E0369]: binary operation `>` cannot be applied to type `&T` --> src/main.rs:5:17 | @@ -319,6 +325,9 @@ help: consider restricting type parameter `T` | 1 | fn largest(list: &[T]) -> &T { | ++++++++++++++++++++++ + +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 @@ -341,9 +350,9 @@ fields using the `<>` syntax. Listing 10-6 defines a `Point` struct to hold Filename: src/main.rs ``` -1 struct Point { - 2 x: T, - 3 y: T, +struct Point { + x: T, + y: T, } fn main() { @@ -352,13 +361,14 @@ fn main() { } ``` -Listing 10-6: A `Point` struct that holds `x` and `y` values of type `T` +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 [1]. Then we use the generic +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 [23]. +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 @@ -379,8 +389,8 @@ fn main() { } ``` -Listing 10-7: The fields `x` and `y` must be the same type because both have -the same generic data type `T`. +Listing 10-7: The fields `x` and `y` must be the same +type because both have the same generic data type `T`. In this example, when we assign the integer value `5` to `x`, we let the compiler know that the generic type `T` will be an integer for this instance of @@ -388,12 +398,16 @@ compiler know that the generic type `T` will be an integer for this instance of same type as `x`, we’ll get a type mismatch error like this: ``` +$ cargo run + Compiling chapter10 v0.1.0 (file:///projects/chapter10) error[E0308]: mismatched types --> src/main.rs:7:38 | 7 | let wont_work = Point { x: 5, y: 4.0 }; - | ^^^ expected integer, found floating- -point number + | ^^^ expected integer, found floating-point number + +For more information about this error, try `rustc --explain E0308`. +error: could not compile `chapter10` (bin "chapter10") due to 1 previous error ``` To define a `Point` struct where `x` and `y` are both generics but could have @@ -416,8 +430,8 @@ fn main() { } ``` -Listing 10-8: A `Point` generic over two types so that `x` and `y` can be -values of different types +Listing 10-8: A `Point` generic over two types so +that `x` and `y` can be values of different types Now all the instances of `Point` shown are allowed! You can use as many generic type parameters in a definition as you want, but using more than a few makes @@ -495,8 +509,9 @@ fn main() { } ``` -Listing 10-9: Implementing a method named `x` on the `Point` struct that -will return a reference to the `x` field of type `T` +Listing 10-9: Implementing a method named `x` on the +`Point` struct that will return a reference to the `x` field of type +`T` Here, we’ve defined a method named `x` on `Point` that returns a reference to the data in the field `x`. @@ -526,8 +541,8 @@ impl Point { } ``` -Listing 10-10: An `impl` block that only applies to a struct with a particular -concrete type for the generic type parameter `T` +Listing 10-10: An `impl` block that only applies to a +struct with a particular concrete type for the generic type parameter `T` This code means the type `Point` will have a `distance_from_origin` method; other instances of `Point` where `T` is not of type `f32` will not @@ -550,11 +565,8 @@ struct Point { y: Y1, } -1 impl Point { - 2 fn mixup( - self, - other: Point, - ) -> Point { +impl Point { + fn mixup(self, other: Point) -> Point { Point { x: self.x, y: other.y, @@ -563,32 +575,32 @@ struct Point { } fn main() { - 3 let p1 = Point { x: 5, y: 10.4 }; - 4 let p2 = Point { x: "Hello", y: 'c' }; + let p1 = Point { x: 5, y: 10.4 }; + let p2 = Point { x: "Hello", y: 'c' }; - 5 let p3 = p1.mixup(p2); + let p3 = p1.mixup(p2); - 6 println!("p3.x = {}, p3.y = {}", p3.x, p3.y); + println!("p3.x = {}, p3.y = {}", p3.x, p3.y); } ``` -Listing 10-11: A method that uses generic types different from its struct’s -definition +Listing 10-11: A method that uses generic types different +from its struct’s definition In `main`, we’ve defined a `Point` that has an `i32` for `x` (with value `5`) -and an `f64` for `y` (with value `10.4` [3]). The `p2` variable is a `Point` -struct that has a string slice for `x` (with value `"Hello"`) and a `char` for -`y` (with value `c` [4]). Calling `mixup` on `p1` with the argument `p2` gives -us `p3` [5], which will have an `i32` for `x` because `x` came from `p1`. The -`p3` variable will have a `char` for `y` because `y` came from `p2`. The -`println!` macro call [6] will print `p3.x = 5, p3.y = c`. +and an `f64` for `y` (with value `10.4`). The `p2` variable is a `Point` struct +that has a string slice for `x` (with value `"Hello"`) and a `char` for `y` +(with value `c`). Calling `mixup` on `p1` with the argument `p2` gives us `p3`, +which will have an `i32` for `x` because `x` came from `p1`. The `p3` variable +will have a `char` for `y` because `y` came from `p2`. The `println!` macro +call will print `p3.x = 5, p3.y = c`. The purpose of this example is to demonstrate a situation in which some generic parameters are declared with `impl` and some are declared with the method definition. Here, the generic parameters `X1` and `Y1` are declared after -`impl` [1] because they go with the struct definition. The generic parameters -`X2` and `Y2` are declared after `fn mixup` [2] because they’re only relevant -to the method. +`impl` because they go with the struct definition. The generic parameters `X2` +and `Y2` are declared after `fn mixup` because they’re only relevant to the +method. ### Performance of Code Using Generics @@ -656,7 +668,7 @@ can use *trait bounds* to specify that a generic type can be any type that has certain behavior. > Note: Traits are similar to a feature often called *interfaces* in other -languages, although with some differences. +> languages, although with some differences. ### Defining a Trait @@ -685,8 +697,8 @@ pub trait Summary { } ``` -Listing 10-12: A `Summary` trait that consists of the behavior provided by a -`summarize` method +Listing 10-12: A `Summary` trait that consists of the +behavior provided by a `summarize` method Here, we declare a trait using the `trait` keyword and then the trait’s name, which is `Summary` in this case. We also declare the trait as `pub` so that @@ -726,12 +738,7 @@ pub struct NewsArticle { impl Summary for NewsArticle { fn summarize(&self) -> String { - format!( - "{}, by {} ({})", - self.headline, - self.author, - self.location - ) + format!("{}, by {} ({})", self.headline, self.author, self.location) } } @@ -749,8 +756,8 @@ impl Summary for Tweet { } ``` -Listing 10-13: Implementing the `Summary` trait on the `NewsArticle` and -`Tweet` types +Listing 10-13: Implementing the `Summary` trait on the +`NewsArticle` and `Tweet` types Implementing a trait on a type is similar to implementing regular methods. The difference is that after `impl`, we put the trait name we want to implement, @@ -828,8 +835,8 @@ pub trait Summary { } ``` -Listing 10-14: Defining a `Summary` trait with a default implementation of the -`summarize` method +Listing 10-14: Defining a `Summary` trait with a default +implementation of the `summarize` method To use a default implementation to summarize instances of `NewsArticle`, we specify an empty `impl` block with `impl Summary for NewsArticle {}`. @@ -840,19 +847,17 @@ directly, we’ve provided a default implementation and specified that the `summarize` method on an instance of `NewsArticle`, like this: ``` -let article = NewsArticle { - headline: String::from( - "Penguins win the Stanley Cup Championship!" - ), - location: String::from("Pittsburgh, PA, USA"), - author: String::from("Iceburgh"), - content: String::from( - "The Pittsburgh Penguins once again are the best \ - hockey team in the NHL.", - ), -}; + let article = NewsArticle { + headline: String::from("Penguins win the Stanley Cup Championship!"), + location: String::from("Pittsburgh, PA, USA"), + author: String::from("Iceburgh"), + content: String::from( + "The Pittsburgh Penguins once again are the best \ + hockey team in the NHL.", + ), + }; -println!("New article available! {}", article.summarize()); + println!("New article available! {}", article.summarize()); ``` This code prints `New article available! (Read more...)`. @@ -875,10 +880,7 @@ pub trait Summary { fn summarize_author(&self) -> String; fn summarize(&self) -> String { - format!( - "(Read more from {}...)", - self.summarize_author() - ) + format!("(Read more from {}...)", self.summarize_author()) } } ``` @@ -902,16 +904,16 @@ definition of `summarize_author` that we’ve provided. Because we’ve implemen that looks like: ``` -let tweet = Tweet { - username: String::from("horse_ebooks"), - content: String::from( - "of course, as you probably already know, people", - ), - reply: false, - retweet: false, -}; + let tweet = Tweet { + username: String::from("horse_ebooks"), + content: String::from( + "of course, as you probably already know, people", + ), + reply: false, + retweet: false, + }; -println!("1 new tweet: {}", tweet.summarize()); + println!("1 new tweet: {}", tweet.summarize()); ``` This code prints `1 new tweet: (Read more from @horse_ebooks...)`. @@ -942,6 +944,9 @@ and pass in any instance of `NewsArticle` or `Tweet`. Code that calls the function with any other type, such as a `String` or an `i32`, won’t compile because those types don’t implement `Summary`. + + + #### Trait Bound Syntax The `impl Trait` syntax works for straightforward cases but is actually syntax @@ -979,7 +984,7 @@ 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 +#### Specifying 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` @@ -999,7 +1004,7 @@ pub fn notify(item: &T) { With the two trait bounds specified, the body of `notify` can call `summarize` and use `{}` to format `item`. -#### Clearer Trait Bounds with where Clauses +#### Clearer Trait Bounds with `where` Clauses Using too many trait bounds has its downsides. Each generic has its own trait bounds, so functions with multiple generic type parameters can contain lots of @@ -1089,20 +1094,21 @@ fn returns_summarizable(switch: bool) -> impl Summary { Returning either a `NewsArticle` or a `Tweet` 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 “Using Trait Objects That Allow -for Values of Different Types” on page XX. +how to write a function with this behavior in the “Using Trait Objects That +Allow for Values of Different +Types” section of Chapter 17. ### 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 “Defining -Methods” on page XX 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 +“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. Filename: src/lib.rs @@ -1131,8 +1137,8 @@ impl Pair { } ``` -Listing 10-15: Conditionally implementing methods on a generic type depending -on trait bounds +Listing 10-15: Conditionally implementing methods on a +generic type depending on trait bounds We can also conditionally implement a trait for any type that implements another trait. Implementations of a trait on any type that satisfies the trait @@ -1143,7 +1149,7 @@ block in the standard library looks similar to this code: ``` impl ToString for T { - --snip-- + // --snip-- } ``` @@ -1177,18 +1183,19 @@ Lifetimes are another kind of generic that we’ve already been using. Rather than ensuring that a type has the behavior we want, lifetimes ensure that references are valid as long as we need them to be. -One detail we didn’t discuss in “References and Borrowing” on page XX is that -every reference in Rust has a *lifetime*, which is the scope for which that -reference is valid. Most of the time, lifetimes are implicit and inferred, just -like most of the time, types are inferred. We must annotate types only 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 +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 reference is valid. Most of the time, lifetimes are implicit and inferred, +just like most of the time, types are inferred. We must annotate types only +when multiple types are possible. In a similar way, we must annotate lifetimes +when the lifetimes of references could be related in a few different ways. Rust requires us to annotate the relationships using generic lifetime parameters to ensure the actual references used at runtime will definitely be valid. -Annotating lifetimes is not even a concept most other programming languages -have, so this is going to feel unfamiliar. Although we won’t cover lifetimes in -their entirety in this chapter, we’ll discuss common ways you might encounter +Annotating lifetimes is not 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. ### Preventing Dangling References with Lifetimes @@ -1200,44 +1207,52 @@ scope. ``` fn main() { - 1 let r; + let r; { - 2 let x = 5; - 3 r = &x; - 4 } + let x = 5; + r = &x; + } - 5 println!("r: {r}"); + println!("r: {r}"); } ``` -Listing 10-16: An attempt to use a reference whose value has gone out of scope +Listing 10-16: An attempt to use a reference whose value +has gone out of scope > Note: The examples in Listing 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 -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 -null values. - -The outer scope declares a variable named `r` with no initial value [1], and -the inner scope declares a variable named `x` with the initial value of `5` -[2]. Inside the inner scope, we attempt to set the value of `r` as a reference -to `x` [3]. Then the inner scope ends [4], and we attempt to print the value in -`r` [5]. 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: - -``` +> 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 +> 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 +> 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: + +``` +$ cargo run + Compiling chapter10 v0.1.0 (file:///projects/chapter10) error[E0597]: `x` does not live long enough --> src/main.rs:6:13 | +5 | let x = 5; + | - binding `x` declared here 6 | r = &x; | ^^ borrowed value does not live long enough 7 | } | - `x` dropped here while still borrowed 8 | -9 | println!("r: {r}"); - | - borrow later used here +9 | println!("r: {}", r); + | - borrow later used here + +For more information about this error, try `rustc --explain E0597`. +error: could not compile `chapter10` (bin "chapter10") due to 1 previous error ``` The error message says that the variable `x` “does not live long enough.” The @@ -1267,8 +1282,8 @@ fn main() { } // ---------+ ``` -Listing 10-17: Annotations of the lifetimes of `r` and `x`, named `'a` and -`'b`, respectively +Listing 10-17: Annotations of the lifetimes of `r` and +`x`, named `'a` and `'b`, respectively Here, we’ve annotated the lifetime of `r` with `'a` and the lifetime of `x` with `'b`. As you can see, the inner `'b` block is much smaller than the outer @@ -1291,14 +1306,14 @@ fn main() { } // ----------+ ``` -Listing 10-18: A valid reference because the data has a longer lifetime than -the reference +Listing 10-18: A valid reference because the data has a +longer lifetime than the reference Here, `x` has the lifetime `'b`, which in this case is larger than `'a`. This 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 +Now that you know what 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. @@ -1321,12 +1336,13 @@ fn main() { } ``` -Listing 10-19: A `main` function that calls the `longest` function to find the -longer of two string slices +Listing 10-19: A `main` function that calls the `longest` +function to find the longer of two string slices Note that we want the function to take string slices, which are references, rather than strings, because we don’t want the `longest` function to take -ownership of its parameters. Refer to “String Slices as Parameters” on page XX +ownership of its parameters. Refer to the “String Slices as +Parameters” section in Chapter 4 for more discussion about why the parameters we use in Listing 10-19 are the ones we want. @@ -1345,24 +1361,29 @@ fn longest(x: &str, y: &str) -> &str { } ``` -Listing 10-20: An implementation of the `longest` function that returns the -longer of two string slices but does not yet compile +Listing 10-20: An implementation of the `longest` +function that returns the longer of two string slices but does not yet +compile Instead, we get the following error that talks about lifetimes: ``` +$ cargo run + Compiling chapter10 v0.1.0 (file:///projects/chapter10) error[E0106]: missing lifetime specifier --> src/main.rs:9:33 | 9 | fn longest(x: &str, y: &str) -> &str { | ---- ---- ^ expected named lifetime parameter | - = help: this function's return type contains a borrowed value, -but the signature does not say whether it is borrowed from `x` or `y` + = help: this function's return type contains a borrowed value, but the signature does not say whether it is borrowed from `x` or `y` help: consider introducing a named lifetime parameter | 9 | fn longest<'a>(x: &'a str, y: &'a str) -> &'a str { | ++++ ++ ++ ++ + +For more information about this error, try `rustc --explain E0106`. +error: could not compile `chapter10` (bin "chapter10") due to 1 previous error ``` The help text reveals that the return type needs a generic lifetime parameter @@ -1435,8 +1456,9 @@ fn longest<'a>(x: &'a str, y: &'a str) -> &'a str { } ``` -Listing 10-21: The `longest` function definition specifying that all the -references in the signature must have the same lifetime `'a` +Listing 10-21: The `longest` function definition +specifying that all the references in the signature must have the same lifetime +`'a` This code should compile and produce the result we want when we use it with the `main` function in Listing 10-19. @@ -1494,8 +1516,8 @@ fn main() { } ``` -Listing 10-22: Using the `longest` function with references to `String` values -that have different concrete lifetimes +Listing 10-22: Using the `longest` function with +references to `String` values that have different concrete lifetimes In this example, `string1` is valid until the end of the outer scope, `string2` is valid until the end of the inner scope, and `result` references something @@ -1525,21 +1547,28 @@ fn main() { } ``` -Listing 10-23: Attempting to use `result` after `string2` has gone out of scope +Listing 10-23: Attempting to use `result` after `string2` +has gone out of scope When we try to compile this code, we get this error: ``` +$ cargo run + Compiling chapter10 v0.1.0 (file:///projects/chapter10) error[E0597]: `string2` does not live long enough --> src/main.rs:6:44 | +5 | let string2 = String::from("xyz"); + | ------- binding `string2` declared here 6 | result = longest(string1.as_str(), string2.as_str()); - | ^^^^^^^^^^^^^^^^ borrowed value -does not live long enough + | ^^^^^^^ borrowed value does not live long enough 7 | } | - `string2` dropped here while still borrowed 8 | println!("The longest string is {result}"); | ------ borrow later used here + +For more information about this error, try `rustc --explain E0597`. +error: could not compile `chapter10` (bin "chapter10") due to 1 previous error ``` The error shows that for `result` to be valid for the `println!` statement, @@ -1604,12 +1633,19 @@ lifetime is not related to the lifetime of the parameters at all. Here is the error message we get: ``` -error[E0515]: cannot return reference to local variable `result` +$ cargo run + Compiling chapter10 v0.1.0 (file:///projects/chapter10) +error[E0515]: cannot return value referencing local variable `result` --> src/main.rs:11:5 | 11 | result.as_str() - | ^^^^^^^^^^^^^^^ returns a reference to data owned by the -current function + | ------^^^^^^^^^ + | | + | returns a value referencing data owned by the current function + | `result` is borrowed here + +For more information about this error, try `rustc --explain E0515`. +error: could not compile `chapter10` (bin "chapter10") due to 1 previous error ``` The problem is that `result` goes out of scope and gets cleaned up at the end @@ -1635,39 +1671,35 @@ on every reference in the struct’s definition. Listing 10-24 has a struct name Filename: src/main.rs ``` -1 struct ImportantExcerpt<'a> { - 2 part: &'a str, +struct ImportantExcerpt<'a> { + part: &'a str, } fn main() { - 3 let novel = String::from( - "Call me Ishmael. Some years ago..." - ); - 4 let first_sentence = novel - .split('.') - .next() - .expect("Could not find a '.'"); - 5 let i = ImportantExcerpt { + let novel = String::from("Call me Ishmael. Some years ago..."); + let first_sentence = novel.split('.').next().expect("Could not find a '.'"); + let i = ImportantExcerpt { part: first_sentence, }; } ``` -Listing 10-24: A struct that holds a reference, requiring a lifetime annotation +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 [2]. As with generic data types, we declare the name of the generic +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 [1]. This +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. The `main` function here creates an instance of the `ImportantExcerpt` struct -[5] that holds a reference to the first sentence of the `String` [4] owned by -the variable `novel` [3]. The data in `novel` exists before the -`ImportantExcerpt` instance is created. In addition, `novel` doesn’t go out of -scope until after the `ImportantExcerpt` goes out of scope, so the reference in -the `ImportantExcerpt` instance is valid. +that holds a reference to the first sentence of the `String` owned by the +variable `novel`. The data in `novel` exists before the `ImportantExcerpt` +instance is created. In addition, `novel` doesn’t go out of scope until after +the `ImportantExcerpt` goes out of scope, so the reference in the +`ImportantExcerpt` instance is valid. ### Lifetime Elision @@ -1692,8 +1724,9 @@ 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 +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 @@ -1924,7 +1957,5 @@ Believe it or not, there is much more to learn on the topics we discussed in this chapter: Chapter 17 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 *https://doc.rust-lang.org/reference/trait-bounds.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/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. diff --git a/src/ch09-00-error-handling.md b/src/ch09-00-error-handling.md index 790955c591..cf997d91b9 100644 --- a/src/ch09-00-error-handling.md +++ b/src/ch09-00-error-handling.md @@ -10,7 +10,7 @@ deployed your code to production! Rust groups errors into two major categories: *recoverable* and *unrecoverable* errors. For a recoverable error, such as a *file not found* error, we most likely just want to report the problem to the user and retry the operation. -Unrecoverable errors are always symptoms of bugs, like trying to access a +Unrecoverable errors are always symptoms of bugs, such as trying to access a location beyond the end of an array, and so we want to immediately stop the program. diff --git a/src/ch09-01-unrecoverable-errors-with-panic.md b/src/ch09-01-unrecoverable-errors-with-panic.md index 3928f8d61a..5c27c8f07f 100644 --- a/src/ch09-01-unrecoverable-errors-with-panic.md +++ b/src/ch09-01-unrecoverable-errors-with-panic.md @@ -1,6 +1,6 @@ ## Unrecoverable Errors with `panic!` -Sometimes, bad things happen in your code, and there’s nothing you can do about +Sometimes bad things happen in your code, and there’s nothing you can do about it. In these cases, Rust has the `panic!` macro. There are two ways to cause a panic in practice: by taking an action that causes our code to panic (such as accessing an array past the end) or by explicitly calling the `panic!` macro. @@ -11,18 +11,18 @@ panic occurs to make it easier to track down the source of the panic. > ### Unwinding the Stack or Aborting in Response to a Panic > -> By default, when a panic occurs, the program starts *unwinding*, which -> means Rust walks back up the stack and cleans up the data from each function -> it encounters. However, this walking back and cleanup is a lot of work. Rust, +> By default, when a panic occurs the program starts *unwinding*, which means +> Rust walks back up the stack and cleans up the data from each function it +> encounters. However, walking back and cleaning up is a lot of work. Rust, > therefore, allows you to choose the alternative of immediately *aborting*, > which ends the program without cleaning up. > -> Memory that the program was using will then need to be cleaned -> up by the operating system. If in your project you need to make the resulting -> binary as small as possible, you can switch from unwinding to aborting upon a -> panic by adding `panic = 'abort'` to the appropriate `[profile]` sections in -> your *Cargo.toml* file. For example, if you want to abort on panic in release -> mode, add this: +> Memory that the program was using will then need to be cleaned up by the +> operating system. If in your project you need to make the resultant binary as +> small as possible, you can switch from unwinding to aborting upon a panic by +> adding `panic = 'abort'` to the appropriate `[profile]` sections in your +> *Cargo.toml* file. For example, if you want to abort on panic in release mode, +> add this: > > ```toml > [profile.release] @@ -52,17 +52,17 @@ In this case, the line indicated is part of our code, and if we go to that line, we see the `panic!` macro call. In other cases, the `panic!` call might be in code that our code calls, and the filename and line number reported by the error message will be someone else’s code where the `panic!` macro is -called, not the line of our code that eventually led to the `panic!` call. We -can use the backtrace of the functions the `panic!` call came from to figure -out the part of our code that is causing the problem. We’ll discuss backtraces -in more detail next. +called, not the line of our code that eventually led to the `panic!` call. -### Using a `panic!` Backtrace + + -Let’s look at another example to see what it’s like when a `panic!` call comes -from a library because of a bug in our code instead of from our code calling -the macro directly. Listing 9-1 has some code that attempts to access an -index in a vector beyond the range of valid indexes. +We can use the backtrace of the functions the `panic!` call came from to figure +out the part of our code that is causing the problem. To understand how to use +a `panic!` backtrace, let’s look at another example and see what it’s like when +a `panic!` call comes from a library because of a bug in our code instead of +from our code calling the macro directly. Listing 9-1 has some code that +attempts to access an index in a vector beyond the range of valid indexes. Filename: src/main.rs @@ -74,10 +74,10 @@ index in a vector beyond the range of valid indexes. end of a vector, which will cause a call to `panic!` Here, we’re attempting to access the 100th element of our vector (which is at -index 99 because indexing starts at zero), but the vector has only 3 elements. -In this situation, Rust will panic. Using `[]` is supposed to return an -element, but if you pass an invalid index, there’s no element that Rust could -return here that would be correct. +index 99 because indexing starts at zero), but the vector has only three +elements. In this situation, Rust will panic. Using `[]` is supposed to return +an element, but if you pass an invalid index, there’s no element that Rust +could return here that would be correct. In C, attempting to read beyond the end of a data structure is undefined behavior. You might get whatever is at the location in memory that would @@ -95,18 +95,20 @@ continue. Let’s try it and see: {{#include ../listings/ch09-error-handling/listing-09-01/output.txt}} ``` -This error points at line 4 of our `main.rs` where we attempt to access index -99. The next note line tells us that we can set the `RUST_BACKTRACE` -environment variable to get a backtrace of exactly what happened to cause the -error. A *backtrace* is a list of all the functions that have been called to -get to this point. Backtraces in Rust work as they do in other languages: the -key to reading the backtrace is to start from the top and read until you see -files you wrote. That’s the spot where the problem originated. The lines above -that spot are code that your code has called; the lines below are code that -called your code. These before-and-after lines might include core Rust code, -standard library code, or crates that you’re using. Let’s try getting a -backtrace by setting the `RUST_BACKTRACE` environment variable to any value -except 0. Listing 9-2 shows output similar to what you’ll see. +This error points at line 4 of our *main.rs* where we attempt to access index +`99` of the vector in `v`. + +The `note:` line tells us that we can set the `RUST_BACKTRACE` environment +variable to get a backtrace of exactly what happened to cause the error. A +*backtrace* is a list of all the functions that have been called to get to this +point. Backtraces in Rust work as they do in other languages: the key to +reading the backtrace is to start from the top and read until you see files you +wrote. That’s the spot where the problem originated. The lines above that spot +are code that your code has called; the lines below are code that called your +code. These before-and-after lines might include core Rust code, standard +library code, or crates that you’re using. Let’s try getting a backtrace by +setting the `RUST_BACKTRACE` environment variable to any value except `0`. +Listing 9-2 shows output similar to what you’ll see. in Chapter 2 that the `Result` enum is defined as having two @@ -23,7 +23,7 @@ the type of the value that will be returned in a success case within the `Ok` variant, and `E` represents the type of the error that will be returned in a failure case within the `Err` variant. Because `Result` has these generic type parameters, we can use the `Result` type and the functions defined on it in -many different situations where the successful value and error value we want to +many different situations where the success value and error value we want to return may differ. Let’s call a function that returns a `Result` value because the function could @@ -52,7 +52,7 @@ In the case where `File::open` succeeds, the value in the variable `greeting_file_result` will be an instance of `Ok` that contains a file handle. In the case where it fails, the value in `greeting_file_result` will be an instance of `Err` that contains more information about the kind of error that -happened. +occurred. We need to add to the code in Listing 9-3 to take different actions depending on the value `File::open` returns. Listing 9-4 shows one way to handle the @@ -91,11 +91,11 @@ As usual, this output tells us exactly what has gone wrong. ### Matching on Different Errors The code in Listing 9-4 will `panic!` no matter why `File::open` failed. -However, we want to take different actions for different failure reasons: if +However, we want to take different actions for different failure reasons. If `File::open` failed because the file doesn’t exist, we want to create the file and return the handle to the new file. If `File::open` failed for any other reason—for example, because we didn’t have permission to open the file—we still -want the code to `panic!` in the same way as it did in Listing 9-4. For this we +want the code to `panic!` in the same way it did in Listing 9-4. For this, we add an inner `match` expression, shown in Listing 9-5. Filename: src/main.rs @@ -127,7 +127,7 @@ file can’t be created, a different error message is printed. The second arm of the outer `match` stays the same, so the program panics on any error besides the missing file error. -> ### Alternatives to Using `match` with `Result` +> #### Alternatives to Using `match` with `Result` > > That’s a lot of `match`! The `match` expression is very useful but also very > much a primitive. In Chapter 13, you’ll learn about closures, which are used @@ -162,7 +162,7 @@ the missing file error. > standard library documentation. Many more of these methods can clean up huge > nested `match` expressions when you’re dealing with errors. -### Shortcuts for Panic on Error: `unwrap` and `expect` +#### Shortcuts for Panic on Error: `unwrap` and `expect` Using `match` works well enough, but it can be a bit verbose and doesn’t always communicate intent well. The `Result` type has many helper methods @@ -227,7 +227,7 @@ information to use in debugging. ### Propagating Errors When a function’s implementation calls something that might fail, instead of -handling the error within the function itself, you can return the error to the +handling the error within the function itself you can return the error to the calling code so that it can decide what to do. This is known as *propagating* the error and gives more control to the calling code, where there might be more information or logic that dictates how the error should be handled than what @@ -254,12 +254,12 @@ This function can be written in a much shorter way, but we’re going to start b doing a lot of it manually in order to explore error handling; at the end, we’ll show the shorter way. Let’s look at the return type of the function first: `Result`. This means the function is returning a -value of the type `Result` where the generic parameter `T` has been -filled in with the concrete type `String`, and the generic type `E` has been +value of the type `Result`, where the generic parameter `T` has been +filled in with the concrete type `String` and the generic type `E` has been filled in with the concrete type `io::Error`. If this function succeeds without any problems, the code that calls this -function will receive an `Ok` value that holds a `String`—the username that +function will receive an `Ok` value that holds a `String`—the `username` that this function read from the file. If this function encounters any problems, the calling code will receive an `Err` value that holds an instance of `io::Error` that contains more information about what the problems were. We chose @@ -277,8 +277,8 @@ keyword to return early out of the function entirely and pass the error value from `File::open`, now in the pattern variable `e`, back to the calling code as this function’s error value. -So if we have a file handle in `username_file`, the function then creates a new -`String` in variable `username` and calls the `read_to_string` method on +So, if we have a file handle in `username_file`, the function then creates a +new `String` in variable `username` and calls the `read_to_string` method on the file handle in `username_file` to read the contents of the file into `username`. The `read_to_string` method also returns a `Result` because it might fail, even though `File::open` succeeded. So we need another `match` to @@ -304,8 +304,8 @@ question mark operator `?` to make this easier. #### A Shortcut for Propagating Errors: the `?` Operator Listing 9-7 shows an implementation of `read_username_from_file` that has the -same functionality as in Listing 9-6, but this implementation uses the -`?` operator. +same functionality as in Listing 9-6, but this implementation uses the `?` +operator. Filename: src/main.rs @@ -410,8 +410,8 @@ as the `match` expression we defined in Listing 9-6. In Listing 9-6, the it’s compatible with this `return`. In Listing 9-10, let’s look at the error we’ll get if we use the `?` operator -in a `main` function with a return type incompatible with the type of the value -we use `?` on: +in a `main` function with a return type that is incompatible with the type of +the value we use `?` on. Filename: src/main.rs @@ -420,7 +420,7 @@ we use `?` on: ``` Listing 9-10: Attempting to use the `?` in the `main` -function that returns `()` won’t compile +function that returns `()` won’t compile. This code opens a file, which might fail. The `?` operator follows the `Result` value returned by `File::open`, but this `main` function has the return type of @@ -437,9 +437,9 @@ function that returns `Result`, `Option`, or another type that implements To fix the error, you have two choices. One choice is to change the return type of your function to be compatible with the value you’re using the `?` operator -on as long as you have no restrictions preventing that. The other technique is -to use a `match` or one of the `Result` methods to handle the `Result` in whatever way is appropriate. +on as long as you have no restrictions preventing that. The other choice is to +use a `match` or one of the `Result` methods to handle the `Result` +in whatever way is appropriate. The error message also mentioned that `?` can be used with `Option` values as well. As with using `?` on `Result`, you can only use `?` on `Option` in a @@ -447,9 +447,9 @@ function that returns an `Option`. The behavior of the `?` operator when called on an `Option` is similar to its behavior when called on a `Result`: if the value is `None`, the `None` will be returned early from the function at that point. If the value is `Some`, the value inside the `Some` is the -resulting value of the expression and the function continues. Listing 9-11 has +resultant value of the expression, and the function continues. Listing 9-11 has an example of a function that finds the last character of the first line in the -given text: +given text. ```rust {{#rustdoc_include ../listings/ch09-error-handling/listing-09-11/src/main.rs:here}} @@ -472,7 +472,7 @@ The `?` extracts the string slice, and we can call `chars` on that string slice to get an iterator of its characters. We’re interested in the last character in this first line, so we call `last` to return the last item in the iterator. This is an `Option` because it’s possible that the first line is the empty -string, for example if `text` starts with a blank line but has characters on +string; for example, if `text` starts with a blank line but has characters on other lines, as in `"\nhi"`. However, if there is a last character on the first line, it will be returned in the `Some` variant. The `?` operator in the middle gives us a concise way to express this logic, allowing us to implement the @@ -487,38 +487,40 @@ you can use methods like the `ok` method on `Result` or the `ok_or` method on `Option` to do the conversion explicitly. So far, all the `main` functions we’ve used return `()`. The `main` function is -special because it’s the entry and exit point of executable programs, and there -are restrictions on what its return type can be for the programs to behave as -expected. +special because it’s the entry point and exit point of an executable program, +and there are restrictions on what its return type can be for the program to +behave as expected. -Luckily, `main` can also return a `Result<(), E>`. Listing 9-12 has the -code from Listing 9-10 but we’ve changed the return type of `main` to be +Luckily, `main` can also return a `Result<(), E>`. Listing 9-12 has the code +from Listing 9-10, but we’ve changed the return type of `main` to be `Result<(), Box>` and added a return value `Ok(())` to the end. This -code will now compile: +code will now compile. + +Filename: src/main.rs ```rust,ignore {{#rustdoc_include ../listings/ch09-error-handling/listing-09-12/src/main.rs}} ``` Listing 9-12: Changing `main` to return `Result<(), E>` -allows the use of the `?` operator on `Result` values +allows the use of the `?` operator on `Result` values. The `Box` type is a *trait object*, which we’ll talk about in the [“Using Trait Objects that Allow for Values of Different Types”][trait-objects] section in Chapter 17. For now, you can read `Box` to mean “any kind of error.” Using `?` on a `Result` -value in a `main` function with the error type `Box` is allowed, +value in a `main` function with the error type `Box` is allowed because it allows any `Err` value to be returned early. Even though the body of this `main` function will only ever return errors of type `std::io::Error`, by specifying `Box`, this signature will continue to be correct even if more code that returns other errors is added to the body of `main`. -When a `main` function returns a `Result<(), E>`, the executable will -exit with a value of `0` if `main` returns `Ok(())` and will exit with a -nonzero value if `main` returns an `Err` value. Executables written in C return -integers when they exit: programs that exit successfully return the integer -`0`, and programs that error return some integer other than `0`. Rust also -returns integers from executables to be compatible with this convention. +When a `main` function returns a `Result<(), E>`, the executable will exit with +a value of `0` if `main` returns `Ok(())` and will exit with a nonzero value if +`main` returns an `Err` value. Executables written in C return integers when +they exit: programs that exit successfully return the integer `0`, and programs +that error return some integer other than `0`. Rust also returns integers from +executables to be compatible with this convention. The `main` function may return any types that implement [the `std::process::Termination` trait][termination], which contains diff --git a/src/ch09-03-to-panic-or-not-to-panic.md b/src/ch09-03-to-panic-or-not-to-panic.md index 7c77cf9331..40f93d67ce 100644 --- a/src/ch09-03-to-panic-or-not-to-panic.md +++ b/src/ch09-03-to-panic-or-not-to-panic.md @@ -19,11 +19,11 @@ some general guidelines on how to decide whether to panic in library code. ### Examples, Prototype Code, and Tests -When you’re writing an example to illustrate some concept, also including robust -error-handling code can make the example less clear. In -examples, it’s understood that a call to a method like `unwrap` that could -panic is meant as a placeholder for the way you’d want your application to -handle errors, which can differ based on what the rest of your code is doing. +When you’re writing an example to illustrate some concept, also including +robust error-handling code can make the example less clear. In examples, it’s +understood that a call to a method like `unwrap` that could panic is meant as a +placeholder for the way you’d want your application to handle errors, which can +differ based on what the rest of your code is doing. Similarly, the `unwrap` and `expect` methods are very handy when prototyping, before you’re ready to decide how to handle errors. They leave clear markers in @@ -60,16 +60,16 @@ valid IP address. If the IP address string came from a user rather than being hardcoded into the program and therefore *did* have a possibility of failure, we’d definitely want to handle the `Result` in a more robust way instead. Mentioning the assumption that this IP address is hardcoded will prompt us to -change `expect` to better error handling code if in the future, we need to get +change `expect` to better error-handling code if, in the future, we need to get the IP address from some other source instead. ### Guidelines for Error Handling -It’s advisable to have your code panic when it’s possible that your code -could end up in a bad state. In this context, a *bad state* is when some -assumption, guarantee, contract, or invariant has been broken, such as when -invalid values, contradictory values, or missing values are passed to your -code—plus one or more of the following: +It’s advisable to have your code panic when it’s possible that your code could +end up in a bad state. In this context, a *bad state* is when some assumption, +guarantee, contract, or invariant has been broken, such as when invalid values, +contradictory values, or missing values are passed to your code—plus one or +more of the following: * The bad state is something that is unexpected, as opposed to something that will likely happen occasionally, like a user entering data in the wrong @@ -104,7 +104,7 @@ an out-of-bounds memory access: trying to access memory that doesn’t belong to the current data structure is a common security problem. Functions often have *contracts*: their behavior is only guaranteed if the inputs meet particular requirements. Panicking when the contract is violated makes sense because a -contract violation always indicates a caller-side bug and it’s not a kind of +contract violation always indicates a caller-side bug, and it’s not a kind of error you want the calling code to have to explicitly handle. In fact, there’s no reasonable way for calling code to recover; the calling *programmers* need to fix the code. Contracts for a function, especially when a violation will @@ -133,13 +133,15 @@ numbers before checking it against our secret number; we only validated that the guess was positive. In this case, the consequences were not very dire: our output of “Too high” or “Too low” would still be correct. But it would be a useful enhancement to guide the user toward valid guesses and have different -behavior when a user guesses a number that’s out of range versus when a user -types, for example, letters instead. +behavior when the user guesses a number that’s out of range versus when the +user types, for example, letters instead. One way to do this would be to parse the guess as an `i32` instead of only a `u32` to allow potentially negative numbers, and then add a check for the number being in range, like so: +Filename: src/main.rs + ```rust,ignore {{#rustdoc_include ../listings/ch09-error-handling/no-listing-09-guess-out-of-range/src/main.rs:here}} ``` @@ -150,7 +152,7 @@ and ask for another guess. After the `if` expression, we can proceed with the comparisons between `guess` and the secret number knowing that `guess` is between 1 and 100. -However, this is not an ideal solution: if it was absolutely critical that the +However, this is not an ideal solution: if it were absolutely critical that the program only operated on values between 1 and 100, and it had many functions with this requirement, having a check like this in every function would be tedious (and might impact performance). @@ -162,19 +164,16 @@ confidently use the values they receive. Listing 9-13 shows one way to define a `Guess` type that will only create an instance of `Guess` if the `new` function receives a value between 1 and 100. - +Filename: src/lib.rs ```rust -{{#include ../listings/ch09-error-handling/listing-09-13/src/main.rs:here}} +{{#rustdoc_include ../listings/ch09-error-handling/listing-09-13/src/lib.rs}} ``` Listing 9-13: A `Guess` type that will only continue with values between 1 and 100 -First, we define a struct named `Guess` that has a field named `value` that +First we define a struct named `Guess` that has a field named `value` that holds an `i32`. This is where the number will be stored. Then we implement an associated function named `new` on `Guess` that creates @@ -193,7 +192,7 @@ to the `value` parameter and return the `Guess`. Next, we implement a method named `value` that borrows `self`, doesn’t have any other parameters, and returns an `i32`. This kind of method is sometimes called -a *getter*, because its purpose is to get some data from its fields and return +a *getter* because its purpose is to get some data from its fields and return it. This public method is necessary because the `value` field of the `Guess` struct is private. It’s important that the `value` field be private so code using the `Guess` struct is not allowed to set `value` directly: code outside @@ -207,7 +206,7 @@ then declare in its signature that it takes or returns a `Guess` rather than an ## Summary -Rust’s error handling features are designed to help you write more robust code. +Rust’s error-handling features are designed to help you write more robust code. The `panic!` macro signals that your program is in a state it can’t handle and lets you tell the process to stop instead of trying to proceed with invalid or incorrect values. The `Result` enum uses Rust’s type system to indicate that diff --git a/src/ch10-00-generics.md b/src/ch10-00-generics.md index bfe7ad3eec..bd18a5e525 100644 --- a/src/ch10-00-generics.md +++ b/src/ch10-00-generics.md @@ -7,13 +7,13 @@ how they relate to other generics without knowing what will be in their place 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 a function takes parameters with -unknown values to run the same code on multiple concrete values. In fact, we’ve -already used generics in Chapter 6 with `Option`, Chapter 8 with `Vec` -and `HashMap`, and Chapter 9 with `Result`. In this chapter, you’ll +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 +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. @@ -32,15 +32,15 @@ help. Generics allow us to replace specific types with a placeholder that represents multiple types to remove code duplication. Before diving into generics syntax, -then, let’s first look at how to remove duplication in a way that doesn’t -involve generic types by extracting a function that replaces specific values -with a placeholder that represents multiple values. Then we’ll apply the same +let’s first look at how to remove duplication in a way that doesn’t involve +generic types by extracting a function that replaces specific values with a +placeholder that represents multiple values. Then we’ll apply the same technique to extract a generic function! By looking at how to recognize duplicated code you can extract into a function, you’ll start to recognize duplicated code that can use generics. -We begin with the short program in Listing 10-1 that finds the largest number -in a list. +We’ll begin with the short program in Listing 10-1 that finds the largest +number in a list. Filename: src/main.rs @@ -54,13 +54,13 @@ numbers We store a list of integers in the variable `number_list` and place a reference to the first number in the list in a variable named `largest`. We then iterate through all the numbers in the list, and if the current number is greater than -the number stored in `largest`, replace the reference in that variable. +the number stored in `largest`, we replace the reference in that variable. However, if the current number is less than or equal to the largest number seen so far, the variable doesn’t change, and the code moves on to the next number in the list. After considering all the numbers in the list, `largest` should refer to the largest number, which in this case is 100. -We've now been tasked with finding the largest number in two different lists of +We’ve now been tasked with finding the largest number in two different lists of numbers. To do so, we can choose to duplicate the code in Listing 10-1 and use the same logic at two different places in the program, as shown in Listing 10-2. @@ -105,9 +105,9 @@ In summary, here are the steps we took to change the code from Listing 10-2 to Listing 10-3: 1. Identify duplicate code. -2. Extract the duplicate code into the body of the function and specify the +1. Extract the duplicate code into the body of the function, and specify the inputs and return values of that code in the function signature. -3. Update the two instances of duplicated code to call the function instead. +1. Update the two instances of duplicated code to call the function instead. Next, we’ll use these same steps with generics to reduce code duplication. In the same way that the function body can operate on an abstract `list` instead diff --git a/src/ch10-01-syntax.md b/src/ch10-01-syntax.md index 431dba9667..0b71c04c5b 100644 --- a/src/ch10-01-syntax.md +++ b/src/ch10-01-syntax.md @@ -13,7 +13,7 @@ parameters and return value. Doing so makes our code more flexible and provides more functionality to callers of our function while preventing code duplication. Continuing with our `largest` function, Listing 10-4 shows two functions that -both find the largest value in a slice. We'll then combine these into a single +both find the largest value in a slice. We’ll then combine these into a single function that uses generics. Filename: src/main.rs @@ -23,7 +23,7 @@ function that uses generics. ``` Listing 10-4: Two functions that differ only in their -names and the types in their signatures +names and in the types in their signatures The `largest_i32` function is the one we extracted in Listing 10-3 that finds the largest `i32` in a slice. The `largest_char` function finds the largest @@ -33,16 +33,16 @@ the duplication by introducing a generic type parameter in a single function. 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 a letter, and -Rust’s type-naming convention is UpperCamelCase. Short for “type,” `T` is the +convention, type parameter names in Rust are short, often just one letter, and +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 -`largest` function, place type name declarations inside angle brackets, `<>`, -between the name of the function and the parameter list, like this: +`largest` function, we place type name declarations inside angle brackets, +`<>`, between the name of the function and the parameter list, like this: ```rust,ignore fn largest(list: &[T]) -> &T { @@ -65,7 +65,7 @@ compile yet, but we’ll fix it later in this chapter. ``` Listing 10-5: The `largest` function using generic type -parameters; this doesn’t yet compile +parameters; this doesn’t compile yet If we compile this code right now, we’ll get this error: @@ -79,7 +79,7 @@ states that the body of `largest` won’t work for all possible types that `T` could be. Because we want to compare values of type `T` in the body, we can only use types whose values can be ordered. To enable comparisons, the standard library has the `std::cmp::PartialOrd` trait that you can implement on types -(see Appendix C for more on this trait). By following the help text's +(see Appendix C for more on this trait). By following the help text’s suggestion, we restrict the types valid for `T` to only those that implement `PartialOrd` and this example will compile, because the standard library implements `PartialOrd` on both `i32` and `char`. @@ -100,9 +100,10 @@ fields using the `<>` syntax. Listing 10-6 defines a `Point` struct to hold 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 @@ -119,9 +120,9 @@ Listing 10-7, our code won’t compile. Listing 10-7: The fields `x` and `y` must be the same type because both have the same generic data type `T`. -In this example, when we assign the integer value 5 to `x`, we let the compiler -know that the generic type `T` will be an integer for this instance of -`Point`. Then when we specify 4.0 for `y`, which we’ve defined to 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: ```console @@ -144,7 +145,7 @@ that `x` and `y` can be values of different types Now all the instances of `Point` shown are allowed! You can use as many generic type parameters in a definition as you want, but using more than a few makes -your code hard to read. If you're finding you need lots of generic types in +your code hard to read. If you’re finding you need lots of generic types in your code, it could indicate that your code needs restructuring into smaller pieces. @@ -194,7 +195,7 @@ avoid duplication by using generic types instead. ### In Method Definitions We can implement methods on structs and enums (as we did in Chapter 5) and use -generic types in their definitions, too. Listing 10-9 shows the `Point` +generic types in their definitions too. Listing 10-9 shows the `Point` struct we defined in Listing 10-6 with a method named `x` implemented on it. Filename: src/main.rs @@ -238,7 +239,7 @@ This code means the type `Point` will have a `distance_from_origin` method; other instances of `Point` where `T` is not of type `f32` will not have this method defined. The method measures how far our point is from the point at coordinates (0.0, 0.0) and uses mathematical operations that are -available only for floating point types. +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 @@ -260,22 +261,22 @@ 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 that has a string slice for `x` (with value `"Hello"`) and a `char` for `y` (with value `c`). Calling `mixup` on `p1` with the argument `p2` gives us `p3`, -which will have an `i32` for `x`, because `x` came from `p1`. The `p3` variable -will have a `char` for `y`, because `y` came from `p2`. The `println!` macro +which will have an `i32` for `x` because `x` came from `p1`. The `p3` variable +will have a `char` for `y` because `y` came from `p2`. The `println!` macro call will print `p3.x = 5, p3.y = c`. The purpose of this example is to demonstrate a situation in which some generic parameters are declared with `impl` and some are declared with the method definition. Here, the generic parameters `X1` and `Y1` are declared after `impl` because they go with the struct definition. The generic parameters `X2` -and `Y2` are declared after `fn mixup`, because they’re only relevant to the +and `Y2` are declared after `fn mixup` because they’re only relevant to the method. ### Performance of Code Using Generics You might be wondering whether there is a runtime cost when using generic type -parameters. The good news is that using generic types won't make your program run -any slower than it would with concrete types. +parameters. The good news is that using generic types won’t make your program +run any slower than it would with concrete types. Rust accomplishes this by performing monomorphization of the code using generics at compile time. *Monomorphization* is the process of turning generic diff --git a/src/ch10-02-traits.md b/src/ch10-02-traits.md index 3c4fb8cad1..53e9b8115c 100644 --- a/src/ch10-02-traits.md +++ b/src/ch10-02-traits.md @@ -1,8 +1,8 @@ ## Traits: Defining Shared Behavior -A *trait* defines functionality a particular type has and can share with other -types. We can use traits to define shared behavior in an abstract way. We can -use *trait bounds* to specify that a generic type can be any type that has +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 +can use *trait bounds* to specify that a generic type can be any type that has certain behavior. > Note: Traits are similar to a feature often called *interfaces* in other @@ -17,15 +17,15 @@ define a set of behaviors necessary to accomplish some purpose. For example, let’s say we have multiple structs that hold various kinds and amounts of text: a `NewsArticle` struct that holds a news story filed in a -particular location and a `Tweet` that can have at most 280 characters along +particular location and a `Tweet` that can have, at most, 280 characters along with metadata that indicates whether it was a new tweet, a retweet, or a reply to another tweet. We want to make a media aggregator library crate named `aggregator` that can display summaries of data that might be stored in a `NewsArticle` or `Tweet` -instance. To do this, we need a summary from each type, and we’ll request -that summary by calling a `summarize` method on an instance. Listing 10-12 -shows the definition of a public `Summary` trait that expresses this behavior. +instance. To do this, we need a summary from each type, and we’ll request that +summary by calling a `summarize` method on an instance. Listing 10-12 shows the +definition of a public `Summary` trait that expresses this behavior. Filename: src/lib.rs @@ -37,7 +37,7 @@ shows the definition of a public `Summary` trait that expresses this behavior. behavior provided by a `summarize` method Here, we declare a trait using the `trait` keyword and then the trait’s name, -which is `Summary` in this case. We’ve also declared the trait as `pub` so that +which is `Summary` in this case. We also declare the trait as `pub` so that crates depending on this crate can make use of this trait too, as we’ll see in a few examples. Inside the curly brackets, we declare the method signatures that describe the behaviors of the types that implement this trait, which in @@ -50,7 +50,7 @@ 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 -one per line and each line ends in a semicolon. +one per line, and each line ends in a semicolon. ### Implementing a Trait on a Type @@ -59,7 +59,7 @@ we can implement it on the types in our media aggregator. Listing 10-13 shows an implementation of the `Summary` trait on the `NewsArticle` struct that uses the headline, the author, and the location to create the return value of `summarize`. For the `Tweet` struct, we define `summarize` as the username -followed by the entire text of the tweet, assuming that tweet content is +followed by the entire text of the tweet, assuming that the tweet content is already limited to 280 characters. Filename: src/lib.rs @@ -95,23 +95,23 @@ know, people`. Other crates that depend on the `aggregator` crate can also bring the `Summary` trait into scope to implement `Summary` on their own types. One restriction to -note is that we can implement a trait on a type only if at least one of the -trait or the type is local to our crate. For example, we can implement standard +note is that we can implement a trait on a type only if either the trait or the +type, or both, are local to our crate. For example, we can implement standard library traits like `Display` on a custom type like `Tweet` as part of our -`aggregator` crate functionality, because the type `Tweet` is local to our +`aggregator` crate functionality because the type `Tweet` is local to our `aggregator` crate. We can also implement `Summary` on `Vec` in our -`aggregator` crate, because the trait `Summary` is local to our `aggregator` +`aggregator` crate because the trait `Summary` is local to our `aggregator` crate. But we can’t implement external traits on external types. For example, we can’t -implement the `Display` trait on `Vec` within our `aggregator` crate, -because `Display` and `Vec` are 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 @@ -120,7 +120,7 @@ in a trait instead of requiring implementations for all methods on every type. Then, as we implement the trait on a particular type, we can keep or override each method’s default behavior. -In Listing 10-14 we specify a default string for the `summarize` method of the +In Listing 10-14, we specify a default string for the `summarize` method of the `Summary` trait instead of only defining the method signature, as we did in Listing 10-12. @@ -175,7 +175,8 @@ After we define `summarize_author`, we can call `summarize` on instances of the `Tweet` struct, and the default implementation of `summarize` will call the definition of `summarize_author` that we’ve provided. Because we’ve implemented `summarize_author`, the `Summary` trait has given us the behavior of the -`summarize` method without requiring us to write any more code. +`summarize` method without requiring us to write any more code. Here’s what +that looks like: ```rust,ignore {{#rustdoc_include ../listings/ch10-generic-types-traits-and-lifetimes/no-listing-03-default-impl-calls-other-methods/src/main.rs:here}} @@ -189,7 +190,7 @@ overriding implementation of that same method. ### Traits as Parameters Now that you know how to define and implement traits, we can explore how to use -traits to define functions that accept many different types. We'll use the +traits to define functions that accept many different types. We’ll use the `Summary` trait we implemented on the `NewsArticle` and `Tweet` types in Listing 10-13 to define a `notify` function that calls the `summarize` method on its `item` parameter, which is of some type that implements the `Summary` @@ -274,7 +275,7 @@ bounds, so functions with multiple generic type parameters can contain lots of trait bound information between the function’s name and its parameter list, making the function signature hard to read. For this reason, Rust has alternate syntax for specifying trait bounds inside a `where` clause after the function -signature. So instead of writing this: +signature. So, instead of writing this: ```rust,ignore fn some_function(t: &T, u: &U) -> i32 { @@ -290,7 +291,7 @@ 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. -### Returning Types that Implement Traits +### Returning Types That Implement Traits We can also use the `impl Trait` syntax in the return position to return a value of some type that implements a trait, as shown here: @@ -349,7 +350,7 @@ generic type depending on trait bounds We can also conditionally implement a trait for any type that implements another trait. Implementations of a trait on any type that satisfies the trait -bounds are called *blanket implementations* and are extensively used in the +bounds are called *blanket implementations* and are used extensively in the Rust standard library. For example, the standard library implements the `ToString` trait on any type that implements the `Display` trait. The `impl` block in the standard library looks similar to this code: @@ -377,12 +378,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 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 time. -Doing so improves performance without having to give up the flexibility of -generics. +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 +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 +time. Doing so improves performance without having to give up the flexibility +of generics. [using-trait-objects-that-allow-for-values-of-different-types]: ch17-02-trait-objects.html#using-trait-objects-that-allow-for-values-of-different-types [methods]: ch05-03-method-syntax.html#defining-methods diff --git a/src/ch10-03-lifetime-syntax.md b/src/ch10-03-lifetime-syntax.md index 197e2c6f59..de12b6c1e5 100644 --- a/src/ch10-03-lifetime-syntax.md +++ b/src/ch10-03-lifetime-syntax.md @@ -8,15 +8,15 @@ One detail we didn’t discuss in the [“References and Borrowing”][references-and-borrowing] section in Chapter 4 is that every reference in Rust has a *lifetime*, which is the scope for which that reference is valid. Most of the time, lifetimes are implicit and inferred, -just like most of the time, types are inferred. We only have to annotate types +just like most of the time, types are inferred. We must annotate types only when multiple types are possible. In a similar way, we must annotate lifetimes when the lifetimes of references could be related in a few different ways. Rust requires us to annotate the relationships using generic lifetime parameters to ensure the actual references used at runtime will definitely be valid. -Annotating lifetimes is not 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 +Annotating lifetimes is not 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. ### Preventing Dangling References with Lifetimes @@ -33,31 +33,31 @@ scope. 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 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 null values. +> Note: The examples in Listing 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 +> 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 +> 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 +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 what the value `r` is referring to has gone out of scope before we -try to use it. Here is the error message: +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}} ``` -The variable `x` doesn’t “live long enough.” The 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 determine that this code is invalid? -It uses a borrow checker. +The error message says that the variable `x` “does not live long enough.” The +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 +determine that this code is invalid? It uses a borrow checker. ### The Borrow Checker @@ -79,7 +79,7 @@ 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. -Listing 10-18 fixes the code so it doesn’t have a dangling reference and +Listing 10-18 fixes the code so it doesn’t have a dangling reference and it compiles without any errors. ```rust @@ -180,7 +180,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. @@ -260,7 +260,7 @@ references to `String` values that have different concrete lifetimes In this example, `string1` is valid until the end of the outer scope, `string2` is valid until the end of the inner scope, and `result` references something -that is valid until the end of the inner scope. Run this code, and you’ll see +that is valid until the end of the inner scope. Run this code and you’ll see that the borrow checker approves; it will compile and print `The longest string is long string is long`. @@ -293,7 +293,7 @@ this because we annotated the lifetimes of the function parameters and return values using the same lifetime parameter `'a`. As humans, we can look at this code and see that `string1` is longer than -`string2` and therefore `result` will contain a reference to `string1`. +`string2`, and therefore, `result` will contain a reference to `string1`. Because `string1` has not gone out of scope yet, a reference to `string1` will still be valid for the `println!` statement. However, the compiler can’t see that the reference is valid in this case. We’ve told Rust that the lifetime of @@ -362,9 +362,9 @@ would create dangling pointers or otherwise violate memory safety. ### Lifetime Annotations 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 +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. Filename: src/main.rs @@ -393,9 +393,9 @@ the `ImportantExcerpt` goes out of scope, so the reference in the ### Lifetime Elision You’ve learned that every reference has a lifetime and that you need to specify -lifetime parameters for functions or structs that use references. However, in -Chapter 4 we had a function in Listing 4-9, shown again in Listing 10-25, that -compiled without lifetime annotations. +lifetime parameters for functions or structs that use references. However, we +had a function in Listing 4-9, shown again in Listing 10-25, that compiled +without lifetime annotations. Filename: src/lib.rs @@ -449,8 +449,8 @@ which it can’t figure out lifetimes, the compiler will stop with an error. These rules apply to `fn` definitions as well as `impl` blocks. The first rule is that the compiler assigns a lifetime parameter to each -parameter that’s a reference. In other words, a function with one parameter gets -one lifetime parameter: `fn foo<'a>(x: &'a i32)`; a function with two +parameter that’s a reference. In other words, a function with one parameter +gets one lifetime parameter: `fn foo<'a>(x: &'a i32)`; a function with two parameters gets two separate lifetime parameters: `fn foo<'a, 'b>(x: &'a i32, y: &'b i32)`; and so on. @@ -526,7 +526,7 @@ 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 +keyword and then used after the struct’s name because those lifetimes are part of the struct’s type. In method signatures inside the `impl` block, references might be tied to the @@ -535,7 +535,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 @@ -567,9 +567,8 @@ string literals have the `'static` lifetime, which we can annotate as follows: let s: &'static str = "I have a static lifetime."; ``` -The text of this string is stored directly in the program’s binary, which -is always available. Therefore, the lifetime of all string literals is -`'static`. +The text of this string is stored directly in the program’s binary, which is +always available. Therefore, the lifetime of all string literals is `'static`. You might see suggestions to use the `'static` lifetime in error messages. But before specifying `'static` as the lifetime for a reference, think about @@ -577,7 +576,7 @@ 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 suggesting the `'static` lifetime results from attempting to create a dangling reference or a mismatch of the available lifetimes. In such cases, the solution -is fixing those problems, not specifying the `'static` lifetime. +is to fix those problems, not to specify the `'static` lifetime. ## Generic Type Parameters, Trait Bounds, and Lifetimes Together