]> git.proxmox.com Git - rustc.git/blame - src/doc/book/second-edition/src/ch09-02-recoverable-errors-with-result.md
New upstream version 1.22.1+dfsg1
[rustc.git] / src / doc / book / second-edition / src / ch09-02-recoverable-errors-with-result.md
CommitLineData
cc61c64b
XL
1## Recoverable Errors with `Result`
2
3Most errors aren’t serious enough to require the program to stop entirely.
4Sometimes, when a function fails, it’s for a reason that we can easily
5interpret and respond to. For example, if we try to open a file and that
6operation fails because the file doesn’t exist, we might want to create the
7file instead of terminating the process.
8
ea8adc8c
XL
9Recall in Chapter 2 in the on “[Handling Potential Failure with the `Result`
10Type][handle_failure]<!-- ignore -->” section that the `Result` enum is defined
cc61c64b
XL
11as having two variants, `Ok` and `Err`, as follows:
12
13[handle_failure]: ch02-00-guessing-game-tutorial.html#handling-potential-failure-with-the-result-type
14
15```rust
16enum Result<T, E> {
17 Ok(T),
18 Err(E),
19}
20```
21
ea8adc8c 22The `T` and `E` are generic type parameters: we’ll discuss generics in more
cc61c64b
XL
23detail in Chapter 10. What you need to know right now is that `T` represents
24the type of the value that will be returned in a success case within the `Ok`
25variant, and `E` represents the type of the error that will be returned in a
26failure case within the `Err` variant. Because `Result` has these generic type
27parameters, we can use the `Result` type and the functions that the standard
28library has defined on it in many different situations where the successful
29value and error value we want to return may differ.
30
31Let’s call a function that returns a `Result` value because the function could
ea8adc8c 32fail: in Listing 9-3 we try to open a file:
cc61c64b
XL
33
34<span class="filename">Filename: src/main.rs</span>
35
36```rust
37use std::fs::File;
38
39fn main() {
40 let f = File::open("hello.txt");
41}
42```
43
ea8adc8c 44<span class="caption">Listing 9-3: Opening a file</span>
cc61c64b
XL
45
46How do we know `File::open` returns a `Result`? We could look at the standard
47library API documentation, or we could ask the compiler! If we give `f` a type
ea8adc8c 48annotation of a type that we know the return type of the function is *not* and
cc61c64b 49then we try to compile the code, the compiler will tell us that the types don’t
ea8adc8c 50match. The error message will then tell us what the type of `f` *is*. Let’s try
cc61c64b 51it: we know that the return type of `File::open` isn’t of type `u32`, so let’s
ea8adc8c 52change the `let f` statement to this:
cc61c64b
XL
53
54```rust,ignore
55let f: u32 = File::open("hello.txt");
56```
57
ea8adc8c 58Attempting to compile now gives us the following output:
cc61c64b
XL
59
60```text
61error[E0308]: mismatched types
62 --> src/main.rs:4:18
63 |
644 | let f: u32 = File::open("hello.txt");
65 | ^^^^^^^^^^^^^^^^^^^^^^^ expected u32, found enum
66`std::result::Result`
67 |
68 = note: expected type `u32`
69 = note: found type `std::result::Result<std::fs::File, std::io::Error>`
70```
71
72This tells us the return type of the `File::open` function is a `Result<T, E>`.
73The generic parameter `T` has been filled in here with the type of the success
74value, `std::fs::File`, which is a file handle. The type of `E` used in the
75error value is `std::io::Error`.
76
77This return type means the call to `File::open` might succeed and return to us
78a file handle that we can read from or write to. The function call also might
ea8adc8c 79fail: for example, the file might not exist or we might not have permission to
cc61c64b 80access the file. The `File::open` function needs to have a way to tell us
ea8adc8c 81whether it succeeded or failed and at the same time give us either the file
cc61c64b
XL
82handle or error information. This information is exactly what the `Result` enum
83conveys.
84
85In the case where `File::open` succeeds, the value we will have in the variable
86`f` will be an instance of `Ok` that contains a file handle. In the case where
87it fails, the value in `f` will be an instance of `Err` that contains more
88information about the kind of error that happened.
89
ea8adc8c
XL
90We need to add to the code in Listing 9-3 to take different actions depending
91on the value `File::open` returned. Listing 9-4 shows one way to handle the
92`Result` using a basic tool: the `match` expression that we discussed in
cc61c64b
XL
93Chapter 6.
94
95<span class="filename">Filename: src/main.rs</span>
96
97```rust,should_panic
98use std::fs::File;
99
100fn main() {
101 let f = File::open("hello.txt");
102
103 let f = match f {
104 Ok(file) => file,
105 Err(error) => {
106 panic!("There was a problem opening the file: {:?}", error)
107 },
108 };
109}
110```
111
ea8adc8c 112<span class="caption">Listing 9-4: Using a `match` expression to handle the
cc61c64b
XL
113`Result` variants we might have</span>
114
115Note that, like the `Option` enum, the `Result` enum and its variants have been
116imported in the prelude, so we don’t need to specify `Result::` before the `Ok`
117and `Err` variants in the `match` arms.
118
119Here we tell Rust that when the result is `Ok`, return the inner `file` value
120out of the `Ok` variant, and we then assign that file handle value to the
121variable `f`. After the `match`, we can then use the file handle for reading or
122writing.
123
124The other arm of the `match` handles the case where we get an `Err` value from
125`File::open`. In this example, we’ve chosen to call the `panic!` macro. If
126there’s no file named *hello.txt* in our current directory and we run this
127code, we’ll see the following output from the `panic!` macro:
128
129```text
130thread 'main' panicked at 'There was a problem opening the file: Error { repr:
131Os { code: 2, message: "No such file or directory" } }', src/main.rs:8
132```
133
ea8adc8c
XL
134As usual, this output tells us exactly what has gone wrong.
135
cc61c64b
XL
136### Matching on Different Errors
137
ea8adc8c
XL
138The code in Listing 9-4 will `panic!` no matter the reason that `File::open`
139failed. What we want to do instead is take different actions for different
140failure reasons: if `File::open` failed because the file doesn’t exist, we want
141to create the file and return the handle to the new file. If `File::open`
142failed for any other reason, for example because we didn’t have permission to
143open the file, we still want the code to `panic!` in the same way as it did in
144Listing 9-4. Look at Listing 9-5, which adds another arm to the `match`:
cc61c64b
XL
145
146<span class="filename">Filename: src/main.rs</span>
147
ea8adc8c
XL
148<!-- ignore this test because otherwise it creates hello.txt which causes other
149tests to fail lol -->
150
cc61c64b
XL
151```rust,ignore
152use std::fs::File;
153use std::io::ErrorKind;
154
155fn main() {
156 let f = File::open("hello.txt");
157
158 let f = match f {
159 Ok(file) => file,
160 Err(ref error) if error.kind() == ErrorKind::NotFound => {
161 match File::create("hello.txt") {
162 Ok(fc) => fc,
163 Err(e) => {
164 panic!(
165 "Tried to create file but there was a problem: {:?}",
166 e
167 )
168 },
169 }
170 },
171 Err(error) => {
172 panic!(
173 "There was a problem opening the file: {:?}",
174 error
175 )
176 },
177 };
178}
179```
180
ea8adc8c 181<span class="caption">Listing 9-5: Handling different kinds of errors in
cc61c64b
XL
182different ways</span>
183
184The type of the value that `File::open` returns inside the `Err` variant is
185`io::Error`, which is a struct provided by the standard library. This struct
186has a method `kind` that we can call to get an `io::ErrorKind` value.
187`io::ErrorKind` is an enum provided by the standard library that has variants
188representing the different kinds of errors that might result from an `io`
ea8adc8c
XL
189operation. The variant we want to use is `ErrorKind::NotFound`, which indicates
190the file we’re trying to open doesn’t exist yet.
cc61c64b
XL
191
192The condition `if error.kind() == ErrorKind::NotFound` is called a *match
193guard*: it’s an extra condition on a `match` arm that further refines the arm’s
ea8adc8c
XL
194pattern. This condition must be true for that arm’s code to be run; otherwise,
195the pattern matching will move on to consider the next arm in the `match`. The
196`ref` in the pattern is needed so `error` is not moved into the guard condition
197but is merely referenced by it. The reason `ref` is used to take a reference in
198a pattern instead of `&` will be covered in detail in Chapter 18. In short, in
199the context of a pattern, `&` matches a reference and gives us its value, but
200`ref` matches a value and gives us a reference to it.
cc61c64b
XL
201
202The condition we want to check in the match guard is whether the value returned
203by `error.kind()` is the `NotFound` variant of the `ErrorKind` enum. If it is,
ea8adc8c
XL
204we try to create the file with `File::create`. However, because `File::create`
205could also fail, we need to add an inner `match` statement as well. When the
cc61c64b 206file can’t be opened, a different error message will be printed. The last arm
ea8adc8c
XL
207of the outer `match` stays the same so the program panics on any error besides
208the missing file error.
cc61c64b
XL
209
210### Shortcuts for Panic on Error: `unwrap` and `expect`
211
212Using `match` works well enough, but it can be a bit verbose and doesn’t always
213communicate intent well. The `Result<T, E>` type has many helper methods
ea8adc8c 214defined on it to do various tasks. One of those methods, called `unwrap`, is a
cc61c64b 215shortcut method that is implemented just like the `match` statement we wrote in
ea8adc8c 216Listing 9-4. If the `Result` value is the `Ok` variant, `unwrap` will return
cc61c64b 217the value inside the `Ok`. If the `Result` is the `Err` variant, `unwrap` will
ea8adc8c
XL
218call the `panic!` macro for us. Here is an example of `unwrap` in action:
219
220<span class="filename">Filename: src/main.rs</span>
cc61c64b
XL
221
222```rust,should_panic
223use std::fs::File;
224
225fn main() {
226 let f = File::open("hello.txt").unwrap();
227}
228```
229
230If we run this code without a *hello.txt* file, we’ll see an error message from
231the `panic!` call that the `unwrap` method makes:
232
233```text
234thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: Error {
235repr: Os { code: 2, message: "No such file or directory" } }',
236/stable-dist-rustc/build/src/libcore/result.rs:868
237```
238
ea8adc8c
XL
239Another method, `expect`, which is similar to `unwrap`, lets us also choose the
240`panic!` error message. Using `expect` instead of `unwrap` and providing good
241error messages can convey your intent and make tracking down the source of a
242panic easier. The syntax of `expect` looks like this:
243
244<span class="filename">Filename: src/main.rs</span>
cc61c64b
XL
245
246```rust,should_panic
247use std::fs::File;
248
249fn main() {
250 let f = File::open("hello.txt").expect("Failed to open hello.txt");
251}
252```
253
254We use `expect` in the same way as `unwrap`: to return the file handle or call
ea8adc8c
XL
255the `panic!` macro. The error message used by `expect` in its call to `panic!`
256will be the parameter that we pass to `expect`, rather than the default
cc61c64b
XL
257`panic!` message that `unwrap` uses. Here’s what it looks like:
258
259```text
260thread 'main' panicked at 'Failed to open hello.txt: Error { repr: Os { code:
2612, message: "No such file or directory" } }',
262/stable-dist-rustc/build/src/libcore/result.rs:868
263```
264
ea8adc8c
XL
265Because this error message starts with the text we specified, `Failed to open
266hello.txt`, it will be easier to find where in the code this error message is
267coming from. If we use `unwrap` in multiple places, it can take more time to
268figure out exactly which `unwrap` is causing the panic because all `unwrap`
269calls that panic print the same message.
270
cc61c64b
XL
271### Propagating Errors
272
ea8adc8c
XL
273When you’re writing a function whose implementation calls something that might
274fail, instead of handling the error within this function, you can return the
275error to the calling code so that it can decide what to do. This is known as
276*propagating* the error and gives more control to the calling code where there
cc61c64b
XL
277might be more information or logic that dictates how the error should be
278handled than what you have available in the context of your code.
279
ea8adc8c 280For example, Listing 9-6 shows a function that reads a username from a file. If
cc61c64b
XL
281the file doesn’t exist or can’t be read, this function will return those errors
282to the code that called this function:
283
ea8adc8c
XL
284<span class="filename">Filename: src/main.rs</span>
285
cc61c64b
XL
286```rust
287use std::io;
288use std::io::Read;
289use std::fs::File;
290
291fn read_username_from_file() -> Result<String, io::Error> {
292 let f = File::open("hello.txt");
293
294 let mut f = match f {
295 Ok(file) => file,
296 Err(e) => return Err(e),
297 };
298
299 let mut s = String::new();
300
301 match f.read_to_string(&mut s) {
302 Ok(_) => Ok(s),
303 Err(e) => Err(e),
304 }
305}
306```
307
ea8adc8c 308<span class="caption">Listing 9-6: A function that returns errors to the
cc61c64b
XL
309calling code using `match`</span>
310
311Let’s look at the return type of the function first: `Result<String,
ea8adc8c 312io::Error>`. This means the function is returning a value of the type
cc61c64b
XL
313`Result<T, E>` where the generic parameter `T` has been filled in with the
314concrete type `String`, and the generic type `E` has been filled in with the
315concrete type `io::Error`. If this function succeeds without any problems, the
ea8adc8c
XL
316code that calls this function will receive an `Ok` value that holds a
317`String`—the username that this function read from the file. If this function
318encounters any problems, the code that calls this function will receive an
319`Err` value that holds an instance of `io::Error` that contains more
320information about what the problems were. We chose `io::Error` as the return
321type of this function because that happens to be the type of the error value
322returned from both of the operations we’re calling in this function’s body that
323might fail: the `File::open` function and the `read_to_string` method.
cc61c64b
XL
324
325The body of the function starts by calling the `File::open` function. Then we
326handle the `Result` value returned with a `match` similar to the `match` in
ea8adc8c 327Listing 9-4, only instead of calling `panic!` in the `Err` case, we return
cc61c64b 328early from this function and pass the error value from `File::open` back to the
ea8adc8c
XL
329calling code as this function’s error value. If `File::open` succeeds, we store
330the file handle in the variable `f` and continue.
cc61c64b
XL
331
332Then we create a new `String` in variable `s` and call the `read_to_string`
ea8adc8c
XL
333method on the file handle in `f` to read the contents of the file into `s`. The
334`read_to_string` method also returns a `Result` because it might fail, even
335though `File::open` succeeded. So we need another `match` to handle that
cc61c64b
XL
336`Result`: if `read_to_string` succeeds, then our function has succeeded, and we
337return the username from the file that’s now in `s` wrapped in an `Ok`. If
338`read_to_string` fails, we return the error value in the same way that we
339returned the error value in the `match` that handled the return value of
ea8adc8c
XL
340`File::open`. However, we don’t need to explicitly say `return`, because this
341is the last expression in the function.
cc61c64b
XL
342
343The code that calls this code will then handle getting either an `Ok` value
344that contains a username or an `Err` value that contains an `io::Error`. We
ea8adc8c
XL
345don’t know what the calling code will do with those values. If the calling code
346gets an `Err` value, it could call `panic!` and crash the program, use a
cc61c64b 347default username, or look up the username from somewhere other than a file, for
ea8adc8c
XL
348example. We don’t have enough information on what the calling code is actually
349trying to do, so we propagate all the success or error information upwards for
350it to handle appropriately.
cc61c64b 351
ea8adc8c
XL
352This pattern of propagating errors is so common in Rust that Rust provides the
353question mark operator `?` to make this easier.
cc61c64b 354
ea8adc8c 355#### A Shortcut for Propagating Errors: `?`
cc61c64b 356
ea8adc8c
XL
357Listing 9-7 shows an implementation of `read_username_from_file` that has the
358same functionality as it had in Listing 9-6, but this implementation uses the
cc61c64b
XL
359question mark operator:
360
ea8adc8c
XL
361<span class="filename">Filename: src/main.rs</span>
362
cc61c64b
XL
363```rust
364use std::io;
365use std::io::Read;
366use std::fs::File;
367
368fn read_username_from_file() -> Result<String, io::Error> {
369 let mut f = File::open("hello.txt")?;
370 let mut s = String::new();
371 f.read_to_string(&mut s)?;
372 Ok(s)
373}
374```
375
ea8adc8c 376<span class="caption">Listing 9-7: A function that returns errors to the
cc61c64b
XL
377calling code using `?`</span>
378
ea8adc8c
XL
379The `?` placed after a `Result` value is defined to work in almost the same way
380as the `match` expressions we defined to handle the `Result` values in Listing
3819-6. If the value of the `Result` is an `Ok`, the value inside the `Ok` will
cc61c64b
XL
382get returned from this expression and the program will continue. If the value
383is an `Err`, the value inside the `Err` will be returned from the whole
ea8adc8c
XL
384function as if we had used the `return` keyword so the error value gets
385propagated to the calling code.
386
387The one difference between the `match` expression from Listing 9-6 and what the
388question mark operator does is that when using the question mark operator,
389error values go through the `from` function defined in the `From` trait in the
390standard library. Many error types implement the `from` function to convert an
391error of one type into an error of another type. When used by the question mark
392operator, the call to the `from` function converts the error type that the
393question mark operator gets into the error type defined in the return type of
394the current function that we’re using `?` in. This is useful when parts of a
395function might fail for many different reasons, but the function returns one
396error type that represents all the ways the function might fail. As long as
397each error type implements the `from` function to define how to convert itself
398to the returned error type, the question mark operator takes care of the
399conversion automatically.
400
401In the context of Listing 9-7, the `?` at the end of the `File::open` call will
cc61c64b 402return the value inside an `Ok` to the variable `f`. If an error occurs, `?`
ea8adc8c
XL
403will return early out of the whole function and give any `Err` value to the
404calling code. The same thing applies to the `?` at the end of the
405`read_to_string` call.
cc61c64b
XL
406
407The `?` eliminates a lot of boilerplate and makes this function’s
408implementation simpler. We could even shorten this code further by chaining
ea8adc8c
XL
409method calls immediately after the `?` as shown in Listing 9-8:
410
411<span class="filename">Filename: src/main.rs</span>
cc61c64b
XL
412
413```rust
414use std::io;
415use std::io::Read;
416use std::fs::File;
417
418fn read_username_from_file() -> Result<String, io::Error> {
419 let mut s = String::new();
420
421 File::open("hello.txt")?.read_to_string(&mut s)?;
422
423 Ok(s)
424}
425```
426
ea8adc8c
XL
427<span class="caption">Listing 9-8: Chaining method calls after the question
428mark operator</span>
429
cc61c64b
XL
430We’ve moved the creation of the new `String` in `s` to the beginning of the
431function; that part hasn’t changed. Instead of creating a variable `f`, we’ve
432chained the call to `read_to_string` directly onto the result of
433`File::open("hello.txt")?`. We still have a `?` at the end of the
434`read_to_string` call, and we still return an `Ok` value containing the
435username in `s` when both `File::open` and `read_to_string` succeed rather than
ea8adc8c
XL
436returning errors. The functionality is again the same as in Listing 9-6 and
437Listing 9-7; this is just a different, more ergonomic way to write it.
cc61c64b 438
ea8adc8c 439#### `?` Can Only Be Used in Functions That Return Result
cc61c64b
XL
440
441The `?` can only be used in functions that have a return type of `Result`,
ea8adc8c
XL
442because it is defined to work in the same way as the `match` expression we
443defined in Listing 9-6. The part of the `match` that requires a return type of
444`Result` is `return Err(e)`, so the return type of the function must be a
cc61c64b
XL
445`Result` to be compatible with this `return`.
446
447Let’s look at what happens if we use `?` in the `main` function, which you’ll
448recall has a return type of `()`:
449
450```rust,ignore
451use std::fs::File;
452
453fn main() {
454 let f = File::open("hello.txt")?;
455}
456```
457
ea8adc8c 458When we compile this code, we get the following error message:
cc61c64b
XL
459
460```text
ea8adc8c
XL
461error[E0277]: the `?` operator can only be used in a function that returns
462`Result` (or another type that implements `std::ops::Try`)
463 --> src/main.rs:4:13
cc61c64b 464 |
ea8adc8c
XL
4654 | let f = File::open("hello.txt")?;
466 | ------------------------
467 | |
468 | cannot use the `?` operator in a function that returns `()`
469 | in this macro invocation
cc61c64b 470 |
ea8adc8c
XL
471 = help: the trait `std::ops::Try` is not implemented for `()`
472 = note: required by `std::ops::Try::from_error`
cc61c64b
XL
473```
474
ea8adc8c
XL
475This error points out that we’re only allowed to use the question mark operator
476in a function that returns `Result`. In functions that don’t return `Result`,
477when you call other functions that return `Result`, you’ll need to use a
478`match` or one of the `Result` methods to handle it instead of using `?` to
479potentially propagate the error to the calling code.
cc61c64b
XL
480
481Now that we’ve discussed the details of calling `panic!` or returning `Result`,
482let’s return to the topic of how to decide which is appropriate to use in which
483cases.