diff options
Diffstat (limited to 'src/doc/book/src/ch09-02-recoverable-errors-with-result.md')
-rw-r--r-- | src/doc/book/src/ch09-02-recoverable-errors-with-result.md | 537 |
1 files changed, 537 insertions, 0 deletions
diff --git a/src/doc/book/src/ch09-02-recoverable-errors-with-result.md b/src/doc/book/src/ch09-02-recoverable-errors-with-result.md new file mode 100644 index 000000000..61931f08d --- /dev/null +++ b/src/doc/book/src/ch09-02-recoverable-errors-with-result.md @@ -0,0 +1,537 @@ +## 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 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 the `Result` +Type”][handle_failure]<!-- ignore --> in Chapter 2 that the `Result` enum is +defined as having two variants, `Ok` and `Err`, as follows: + +```rust +enum Result<T, E> { + Ok(T), + Err(E), +} +``` + +The `T` and `E` are generic type parameters: we’ll discuss generics in more +detail in Chapter 10. What you need to know right now is that `T` represents +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 +return may differ. + +Let’s call a function that returns a `Result` value because the function could +fail. In Listing 9-3 we try to open a file. + +<span class="filename">Filename: src/main.rs</span> + +```rust +{{#rustdoc_include ../listings/ch09-error-handling/listing-09-03/src/main.rs}} +``` + +<span class="caption">Listing 9-3: Opening a file</span> + +The return type of `File::open` is a `Result<T, E>`. The generic parameter `T` +has been filled in by the implementation of `File::open` with the type of the +success value, `std::fs::File`, which is a file handle. The type of `E` used in +the error value is `std::io::Error`. This return type means the call to +`File::open` might succeed and return a file handle that we can read from or +write to. The function call also might fail: for example, the file might not +exist, or we might not have permission to access the file. The `File::open` +function needs to have a way to tell us whether it succeeded or failed and at +the same time give us either the file handle or error information. This +information is exactly what the `Result` enum conveys. + +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. + +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 +`Result` using a basic tool, the `match` expression that we discussed in +Chapter 6. + +<span class="filename">Filename: src/main.rs</span> + +```rust,should_panic +{{#rustdoc_include ../listings/ch09-error-handling/listing-09-04/src/main.rs}} +``` + +<span class="caption">Listing 9-4: Using a `match` expression to handle the +`Result` variants that might be returned</span> + +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::` +before the `Ok` and `Err` variants in the `match` arms. + +When the result is `Ok`, this code will return the inner `file` value out of +the `Ok` variant, and we then assign that file handle value to the variable +`greeting_file`. After the `match`, we can use the file handle for reading or +writing. + +The other arm of the `match` handles the case where we get an `Err` value from +`File::open`. In this example, we’ve chosen to call the `panic!` macro. If +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: + +```console +{{#include ../listings/ch09-error-handling/listing-09-04/output.txt}} +``` + +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 +`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 +add an inner `match` expression, shown in Listing 9-5. + +<span class="filename">Filename: src/main.rs</span> + +<!-- ignore this test because otherwise it creates hello.txt which causes other +tests to fail lol --> + +```rust,ignore +{{#rustdoc_include ../listings/ch09-error-handling/listing-09-05/src/main.rs}} +``` + +<span class="caption">Listing 9-5: Handling different kinds of errors in +different ways</span> + +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 +has a method `kind` that we can call to get an `io::ErrorKind` value. The enum +`io::ErrorKind` is provided by the standard library and has variants +representing the different kinds of errors that might result from an `io` +operation. The variant we want to use is `ErrorKind::NotFound`, which indicates +the file we’re trying to open doesn’t exist yet. So we match on +`greeting_file_result`, but we also have an inner match on `error.kind()`. + +The condition we want to check in the inner match is whether the value returned +by `error.kind()` is the `NotFound` variant of the `ErrorKind` enum. If it is, +we try to create the file with `File::create`. However, because `File::create` +could also fail, we need a second arm in the inner `match` expression. When the +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<T, E>` +> +> 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<T, E>`. These methods can be more +> concise than using `match` when handling `Result<T, E>` 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: +> +> <!-- CAN'T EXTRACT SEE https://github.com/rust-lang/mdBook/issues/1127 --> +> +> ```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` + +Using `match` works well enough, but it can be a bit verbose and doesn’t always +communicate intent well. The `Result<T, E>` type has many helper methods +defined on it to do various, more specific tasks. The `unwrap` method is a +shortcut method implemented just like the `match` expression we wrote in +Listing 9-4. If the `Result` value is the `Ok` variant, `unwrap` will return +the value inside the `Ok`. If the `Result` is the `Err` variant, `unwrap` will +call the `panic!` macro for us. Here is an example of `unwrap` in action: + +<span class="filename">Filename: src/main.rs</span> + +```rust,should_panic +{{#rustdoc_include ../listings/ch09-error-handling/no-listing-04-unwrap/src/main.rs}} +``` + +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: + +<!-- manual-regeneration +cd listings/ch09-error-handling/no-listing-04-unwrap +cargo run +copy and paste relevant text +--> + +```text +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 +``` + +Similarly, the `expect` method lets us also choose the `panic!` error message. +Using `expect` instead of `unwrap` and providing good error messages can convey +your intent and make tracking down the source of a panic easier. The syntax of +`expect` looks like this: + +<span class="filename">Filename: src/main.rs</span> + +```rust,should_panic +{{#rustdoc_include ../listings/ch09-error-handling/no-listing-05-expect/src/main.rs}} +``` + +We use `expect` in the same way as `unwrap`: to return the file handle or call +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: + +<!-- manual-regeneration +cd listings/ch09-error-handling/no-listing-05-expect +cargo run +copy and paste relevant text +--> + +```text +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 +``` + +In production-quality code, most Rustaceans choose `expect` rather than +`unwrap` and give more context about why the operation is expected to always +succeed. That way, if your assumptions are ever proven wrong, you have more +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 +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 +you have available in the context of your code. + +For example, Listing 9-6 shows a function that reads a username from a file. If +the file doesn’t exist or can’t be read, this function will return those errors +to the code that called the function. + +<span class="filename">Filename: src/main.rs</span> + +<!-- Deliberately not using rustdoc_include here; the `main` function in the +file panics. We do want to include it for reader experimentation purposes, but +don't want to include it for rustdoc testing purposes. --> + +```rust +{{#include ../listings/ch09-error-handling/listing-09-06/src/main.rs:here}} +``` + +<span class="caption">Listing 9-6: A function that returns errors to the +calling code using `match`</span> + +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<String, io::Error>`. This means the function is returning a +value of the type `Result<T, E>` 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. 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` 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. + +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 +up to the calling code to decide what to do with those values. If the calling +code gets an `Err` value, it could call `panic!` and crash the program, use a +default username, or look up the username from somewhere other than a file, for +example. We don’t have enough information on what the calling code is actually +trying to do, so we propagate all the success or error information upward for +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 + +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. + +<span class="filename">Filename: src/main.rs</span> + +<!-- Deliberately not using rustdoc_include here; the `main` function in the +file panics. We do want to include it for reader experimentation purposes, but +don't want to include it for rustdoc testing purposes. --> + +```rust +{{#include ../listings/ch09-error-handling/listing-09-07/src/main.rs:here}} +``` + +<span class="caption">Listing 9-7: A function that returns errors to the +calling code using the `?` operator</span> + +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 +9-6. If the value of the `Result` is an `Ok`, the value inside the `Ok` will +get returned from this expression, and the program will continue. If the value +is an `Err`, the `Err` will be returned from the whole function as if we had +used the `return` keyword so the error value gets propagated to the calling +code. + +There is a difference between what the `match` expression from Listing 9-6 does +and what the `?` operator does: error values that have the `?` operator called +on them go through the `from` function, defined in the `From` trait in the +standard library, which is used to convert values from one type into another. +When the `?` operator calls the `from` function, the error type received is +converted into the error type defined in the return type of the current +function. This is useful when a function returns one error type to represent +all the ways a function might fail, even if parts might fail for many different +reasons. + +For example, we could change the `read_username_from_file` function in Listing +9-7 to return a custom error type named `OurError` that we define. If we also +define `impl From<io::Error> for OurError` to construct an instance of +`OurError` from an `io::Error`, then the `?` operator calls in the body of +`read_username_from_file` will call `from` and convert the error types without +needing to add any more code to the function. + +In the context of Listing 9-7, the `?` at the end of the `File::open` call will +return the value inside an `Ok` to the variable `username_file`. If an error +occurs, the `?` operator will return early out of the whole function and give +any `Err` value to the calling code. The same thing applies to the `?` at the +end of the `read_to_string` call. + +The `?` operator eliminates a lot of boilerplate and makes this function’s +implementation simpler. We could even shorten this code further by chaining +method calls immediately after the `?`, as shown in Listing 9-8. + +<span class="filename">Filename: src/main.rs</span> + +<!-- Deliberately not using rustdoc_include here; the `main` function in the +file panics. We do want to include it for reader experimentation purposes, but +don't want to include it for rustdoc testing purposes. --> + +```rust +{{#include ../listings/ch09-error-handling/listing-09-08/src/main.rs:here}} +``` + +<span class="caption">Listing 9-8: Chaining method calls after the `?` +operator</span> + +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 +`username_file`, we’ve chained the call to `read_to_string` directly onto the +result of `File::open("hello.txt")?`. We still have a `?` at the end of the +`read_to_string` call, and we still return an `Ok` value containing `username` +when both `File::open` and `read_to_string` succeed rather than returning +errors. The functionality is again the same as in Listing 9-6 and Listing 9-7; +this is just a different, more ergonomic way to write it. + +Listing 9-9 shows a way to make this even shorter using `fs::read_to_string`. + +<span class="filename">Filename: src/main.rs</span> + +<!-- Deliberately not using rustdoc_include here; the `main` function in the +file panics. We do want to include it for reader experimentation purposes, but +don't want to include it for rustdoc testing purposes. --> + +```rust +{{#include ../listings/ch09-error-handling/listing-09-09/src/main.rs:here}} +``` + +<span class="caption">Listing 9-9: Using `fs::read_to_string` instead of +opening and then reading the file</span> + +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 +file, creates a new `String`, reads the contents of the file, puts the contents +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 + +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 +to perform an early return of a value out of the function, in the same manner +as the `match` expression we defined in Listing 9-6. In Listing 9-6, the +`match` was using a `Result` value, and the early return arm returned an +`Err(e)` value. The return type of the function has to be a `Result` so that +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: + +<span class="filename">Filename: src/main.rs</span> + +```rust,ignore,does_not_compile +{{#rustdoc_include ../listings/ch09-error-handling/listing-09-10/src/main.rs}} +``` + +<span class="caption">Listing 9-10: Attempting to use the `?` in the `main` +function that returns `()` won’t compile</span> + +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 +`()`, not `Result`. When we compile this code, we get the following error +message: + +```console +{{#include ../listings/ch09-error-handling/listing-09-10/output.txt}} +``` + +This error points out that we’re only allowed to use the `?` operator in a +function that returns `Result`, `Option`, or another type that implements +`FromResidual`. + +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<T, E>` methods to handle the `Result<T, +E>` in whatever way is appropriate. + +The error message also mentioned that `?` can be used with `Option<T>` values +as well. As with using `?` on `Result`, you can only use `?` on `Option` in a +function that returns an `Option`. The behavior of the `?` operator when called +on an `Option<T>` is similar to its behavior when called on a `Result<T, E>`: +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 +an example of a function that finds the last character of the first line in the +given text: + +```rust +{{#rustdoc_include ../listings/ch09-error-handling/listing-09-11/src/main.rs:here}} +``` + +<span class="caption">Listing 9-11: Using the `?` operator on an `Option<T>` +value</span> + +This function returns `Option<char>` because it’s possible that there is a +character there, but it’s also possible that there isn’t. This code takes the +`text` string slice argument and calls the `lines` method on it, which returns +an iterator over the lines in the string. Because this function wants to +examine the first line, it calls `next` on the iterator to get the first value +from the iterator. If `text` is the empty string, this call to `next` will +return `None`, in which case we use `?` to stop and return `None` from +`last_char_of_first_line`. If `text` is not the empty string, `next` will +return a `Some` value containing a string slice of the first line in `text`. + +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 +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 +function in one line. If we couldn’t use the `?` operator on `Option`, we’d +have to implement this logic using more method calls or a `match` expression. + +Note that you can use the `?` operator on a `Result` in a function that returns +`Result`, and you can use the `?` operator on an `Option` in a function that +returns `Option`, but you can’t mix and match. The `?` operator won’t +automatically convert a `Result` to an `Option` or vice versa; in those cases, +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. + +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<dyn Error>>` and added a return value `Ok(())` to the end. This +code will now compile: + +```rust,ignore +{{#rustdoc_include ../listings/ch09-error-handling/listing-09-12/src/main.rs}} +``` + +<span class="caption">Listing 9-12: Changing `main` to return `Result<(), E>` +allows the use of the `?` operator on `Result` values</span> + +The `Box<dyn Error>` type is a *trait object*, which we’ll talk about in the +[“Using Trait Objects that Allow for Values of Different +Types”][trait-objects]<!-- ignore --> section in Chapter 17. For now, you can +read `Box<dyn Error>` to mean “any kind of error.” Using `?` on a `Result` +value in a `main` function with the error type `Box<dyn Error>` 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<dyn Error>`, 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. + +The `main` function may return any types that implement [the +`std::process::Termination` trait][termination]<!-- ignore -->, 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. + +[handle_failure]: ch02-00-guessing-game-tutorial.html#handling-potential-failure-with-the-result-type +[trait-objects]: ch17-02-trait-objects.html#using-trait-objects-that-allow-for-values-of-different-types +[termination]: ../std/process/trait.Termination.html |