From a28453e40416afa5475d1bcd3d9a165d63f72301 Mon Sep 17 00:00:00 2001 From: Steve Klabnik Date: Thu, 14 Jul 2016 16:54:17 -0400 Subject: [PATCH 01/53] start of error handling --- src/SUMMARY.md | 5 +- src/ch07-01-error-handling.md | 30 +++ ...ch07-02-unrecoverable-errors-with-panic.md | 235 ++++++++++++++++++ src/ch07-03-recoverable-errors-with-result.md | 116 +++++++++ src/ch07-04-creating-your-own-error-types.md | 1 + 5 files changed, 386 insertions(+), 1 deletion(-) create mode 100644 src/ch07-01-error-handling.md create mode 100644 src/ch07-02-unrecoverable-errors-with-panic.md create mode 100644 src/ch07-03-recoverable-errors-with-result.md create mode 100644 src/ch07-04-creating-your-own-error-types.md diff --git a/src/SUMMARY.md b/src/SUMMARY.md index 8e4ad4a8b1..2583ec7179 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -6,6 +6,7 @@ - [Guessing Game Tutorial](ch02-00-guessing-game-tutorial.md) +<<<<<<< a2bc57618340b6257b9a4dd7d4f3993c71cde2e4 - [Common Programming Concepts in Rust](ch03-00-common-programming-concepts-in-rust.md) - [Variable Bindings and Mutability](ch03-01-variable-bindings-and-mutability.md) - [Data Types](ch03-02-data-types.md) @@ -36,7 +37,9 @@ - [Strings]() - [`HashMap`]() -- [Error Handling]() +- [Error Handling](ch07-01-error-handling.md) + - [Unrecoverable errors with panic!](ch07-02-unrecoverable-errors-with-panic.md) + - [Recoverable errors with `Result`](ch07-03-recoverable-errors-with-result.md) - [Lifetimes]() diff --git a/src/ch07-01-error-handling.md b/src/ch07-01-error-handling.md new file mode 100644 index 0000000000..5defc4e8b7 --- /dev/null +++ b/src/ch07-01-error-handling.md @@ -0,0 +1,30 @@ +# Error Handling + +Rust's laser-focus on safety spills over into a related area: error handling. +Errors are a fact of life in software. Rust has a number of tools that you can +use to handle things when something bad happens. + +Rust splits errors into two major kinds: errors that are recoverable, and +errors that are not recoverable. It has two different strategies to handle +these two different forms of errors. + +What does it mean to "recover" from an error? In the simplest sense, it +relates to the answer of this question: + +> If I call a function, and something bad happens, can I do something +> meaningful? Or should execution stop? + +Some kinds of problems have solutions, but with other kinds of errors, +all you can do is throw up your hands and give up. + +We'll start off our examination of error handling by talking about the +unrecoverable case first. Why? You can think of unrecoverable errors as a +subset of recoverable errors. If you choose to treat an error as unrecoverable, +then the function that calls your code has no choice in the matter. However, if +your function returns a recoverable error, the calling function has a choice: +handle the error properly, or convert it into an unrecoverable error. This is +one of the reasons that most Rust code chooses to treat errors as recoverable: +it's more flexible. We're going to explore an example that starts off as +unrecoverable, and then, in the next section, we will convert it to a +convertable error. Finally, we'll talk about how to create our own custom error +types. diff --git a/src/ch07-02-unrecoverable-errors-with-panic.md b/src/ch07-02-unrecoverable-errors-with-panic.md new file mode 100644 index 0000000000..4b56bf5026 --- /dev/null +++ b/src/ch07-02-unrecoverable-errors-with-panic.md @@ -0,0 +1,235 @@ +# Unrecoverable errors with panic! + +You've already seen the way to signal an unrecoverable error: the `panic!` +macro. Here's an example of using `panic!`: + +```rust +fn check_guess(number: u32) -> bool { + if number > 100 { + panic!("Guess was too big: {}", number); + } + + number == 34 +} +``` + +This function accepts a guess between zero and a hundred, and checks if it's +equivalent to the correct number, which is `34` in this case. It's kind of a +silly function, but we need an example, so it works. + +There's no number type for "between zero and a hundred" in Rust, so we are +accepting a `u32`, and then checking in the function's body to make sure the +guess is in-bounds. If the number is too big, there's been an error: someone +made a mistake. We then invoke `panic!` to say "something went wrong, we cannot +continue to run this program." + +Checking some sort of condition and then `panic!`ing if something is wrong is a +common use of `panic!`. In fact, there's a second macro in Rust, `assert!` for +this case. `assert!` will check some kind of condition, and `panic!` if the +condition is false. We could also write our function like this: + +```rust +fn check_guess(number: u32) -> bool { + assert!(number < 100); + + number == 34 +} +``` + +If we try and use `check_guess` in a program, and make an error: + +```rust,should_panic +fn check_guess(number: u32) -> bool { + assert!(number < 100); + + number == 34 +} + +fn main() { + let answer = check_guess(5); + println!("answer was: {}", answer); + + let answer = check_guess(34); + println!("answer was: {}", answer); + + let answer = check_guess(500); + println!("answer was: {}", answer); +} +``` + +We'll see output like this: + +```text +answer was: false +answer was: true + +thread '
' panicked at 'assertion failed: number < 100', :2 +``` + +First, `5` was okay, but false. Then, `34` was okay, but true. Finally, `500` +caused a panic. + +Panics cause your program to stop executing. To check this, we could move the +failing case above the good cases: + +```rust,should_panic +# fn check_guess(number: u32) -> bool { +# assert!(number < 100); +# +# number == 34 +# } +# +fn main() { + let answer = check_guess(500); + println!("answer was: {}", answer); + + let answer = check_guess(5); + println!("answer was: {}", answer); + + let answer = check_guess(34); + println!("answer was: {}", answer); +} +``` + +If we run it, we'll see that we never check `5` or `34`: + +```text +thread '
' panicked at 'assertion failed: number < 100', :2 +``` + +This is why we call `panic!` an unrecoverable error: the other code never gets a +chance to run. Our program just ends. + +But what does it mean to "end a program"? As it turns out, there are multiple +strategies for processing an unrecoverable error. The two main ones are +'unwinding' and 'aborting'. + +## Unwinding + +By default, when a `panic!` happens in Rust, it starts doing something called +"unwinding". To explain unwinding, let's consider a slightly more complex +program: + +```rust,should_panic +fn step1() { + let s = String::from("Step 1"); + step2(); +} + +fn step2() { + let s = String::from("Step 2"); + step3(); +} + +fn step3() { + let s = String::from("Step 3"); + check_guess(500); +} + +fn check_guess(number: u32) -> bool { + assert!(number < 100); + + number == 34 +} + +fn main() { + step1(); +} +``` + +Here, we have four functions total: `step1` calls `step2` which calls `step3` +which then calls `check_guess`. Something like this diagram, with each of the +variable bindings written in: + +```text +> main + | + > step1 String: "Step 1" + | + > step2 String: "Step 2" + | + > step3 String: "Step 3" + | + > check_guess: u32: 500 +``` + +When `check_guess` causes a `panic!` via `assert!`, it will walk back through +each of these functions, and clean them up. We haven't yet talked about +destructors in Rust, that'll come in Chapter XX. For now, think about it this +way: simple values like that `u32` can be destroyed by freeing their memory. +More complicated values, like `String`s, have more complicated needs. In these +cases, there is a function that does this cleanup. We call this a "drop +function" in Rust. + +So, the first thing that will happen is that `check_guess`, our current +function, gets cleaned up. There's only one value, the `500`, and so its memory +will be freed. Now our program looks like this: + +```text +> main + | + > step1 String: "Step 1" + | + > step2 String: "Step 2" + | + > step3 String: "Step 3" +``` + +Now we're on `step3`. In the same way, Rust will call the `String`'s drop +function, deallocating the `String`. Once that's done, we can move on: + +```text +> main + | + > step1 String: "Step 1" + | + > step2 String: "Step 2" +``` + +The pattern continues: `step2` has a single value, and `"Step 2"` has its +drop function called. Next! + +```text +> main + | + > step1 String: "Step 1" +``` + +Almost done! `step1` also has a `String`, so Rust will invoke its drop function. + +```text +> main +``` + +Finally, all we have left is `main()`. It has no variable bindings or arguments +in this case, so there's nothing to clean up. Our whole program has been delt +with, and so terminates execution, after printing a message. + +## Aborting + +Doing all that is a lot of work! And the end result is that our program +terminates. Handing panics with unwinding is useful in many scenarios, but some +applications would rather skip straight to the end, and 'abort'. With some +configuration, Cargo will allow us to use this alternate implementation of +`panic!`. What happens when that's enabled? Let's consider what our call stack +looked like: + +```text +> main + | + > step1 String: "Step 1" + | + > step2 String: "Step 2" + | + > step3 String: "Step 3" + | + > check_guess: u32: 500 +``` + +With an abort implementation of `panic!`, instead of walking back up through the +previous functions and cleaning everything up, we skip straight to the end: our +program terminates. But what about our resources? In the case of memory, like is +the case with `String`s, the operating system will reclaim the memory. So for +this program, the two cases are identical, but aborting is much more efficient. +Other, more complex resources work differently, however, and so aborting may not +always be the right choice either. It depends! diff --git a/src/ch07-03-recoverable-errors-with-result.md b/src/ch07-03-recoverable-errors-with-result.md new file mode 100644 index 0000000000..d341ae7ce6 --- /dev/null +++ b/src/ch07-03-recoverable-errors-with-result.md @@ -0,0 +1,116 @@ +# Recoverable errors with `Result` + +The vast majority of errors in Rust are able to be recovered from. For this +case, Rust has a special type, `Result`, to signal that a function might +succeed or fail. + +Let's take a look at our example function from the last section: + +```rust +fn check_guess(number: u32) -> bool { + if number > 100 { + panic!("Guess was too big: {}", number); + } + + number == 34 +} +``` + +We don't want the entire program to end if our `number` was incorrect. That's a +bit harsh! Instead, we want to signal that an error occurred. Here's a version +of `check_guess` that uses `Result`: + +```rust +fn check_guess(number: u32) -> Result { + if number > 100 { + return Err("Number was out of range"); + } + + Ok(number == 34) +} +``` + +There are three big changes here: to the return type, to the error case, and to +the non-error case. Let's look at each in turn. + +```rust +fn check_guess(number: u32) -> Result { +# if number > 100 { +# return Err("Number was out of range"); +# } +# +# Ok(number == 34) +# } +``` + +Originally, we returned a `bool`, but now we return a +`Result`. This is a type [provided by the standard library] +specifically for indicating that a function might have an error. More +specifically, it's an [`enum`] that looks like this: + +```rust +pub enum Result { + Ok(T), + Err(E), +} +``` + +[provided by the standard library]: https://doc.rust-lang.org/stable/std/result/enum.Result.html +[`enum]`: ch06-01-enums.html + +`Result` is generic over two types: `T`, which is the successful case, and +`E`, which is the error case. It has two variants, `Ok` and `Err`, which also +correspond to these cases, respectively. So the type `Result` means that in the successful, `Ok` case, we will be returning a `bool`. +But in the failure, `Err` case, we will be returning a string literal. + +```rust +# fn check_guess(number: u32) -> Result { +# if number > 100 { + return Err("Number was out of range"); +# } +# +# Ok(number == 34) +# } +``` + +The second change we need to make is to our error case. Instead of causing a +`panic!`, we now `return` an `Err`, with a string literal inside. Remember, +`Result` is an enum: `Err` is one of its variants. + +```rust +# fn check_guess(number: u32) -> Result { +# if number > 100 { +# return Err("Number was out of range"); +# } +# + Ok(number == 34) +# } +``` + +We also need to handle the successful case as well, and we make a change +similarly to the error case: we wrap our return value in `Ok`, which gives it +the correct type. + +## Handling an error + +Let's examine how to use this function: + +```rust +fn check_guess(number: u32) -> Result { + if number > 100 { + return Err("Number was out of range"); + } + + Ok(number == 34) +} + +fn main() { + let answer = check_guess(5); + + match answer { + Ok(b) => println!("answer: {}", b), + Err(e) => println!("There was an error: {}, e"), + }; +} +``` diff --git a/src/ch07-04-creating-your-own-error-types.md b/src/ch07-04-creating-your-own-error-types.md new file mode 100644 index 0000000000..102abe0d75 --- /dev/null +++ b/src/ch07-04-creating-your-own-error-types.md @@ -0,0 +1 @@ +# Creating your own Error types From 4723782107f8cc4e6f7d2d35c3b2267e3b4d8961 Mon Sep 17 00:00:00 2001 From: Steve Klabnik Date: Fri, 19 Aug 2016 17:29:12 -0400 Subject: [PATCH 02/53] move towards vectors --- ...ch07-02-unrecoverable-errors-with-panic.md | 223 +----------------- 1 file changed, 12 insertions(+), 211 deletions(-) diff --git a/src/ch07-02-unrecoverable-errors-with-panic.md b/src/ch07-02-unrecoverable-errors-with-panic.md index 4b56bf5026..5fdea323af 100644 --- a/src/ch07-02-unrecoverable-errors-with-panic.md +++ b/src/ch07-02-unrecoverable-errors-with-panic.md @@ -15,221 +15,22 @@ fn check_guess(number: u32) -> bool { This function accepts a guess between zero and a hundred, and checks if it's equivalent to the correct number, which is `34` in this case. It's kind of a -silly function, but we need an example, so it works. - -There's no number type for "between zero and a hundred" in Rust, so we are -accepting a `u32`, and then checking in the function's body to make sure the -guess is in-bounds. If the number is too big, there's been an error: someone -made a mistake. We then invoke `panic!` to say "something went wrong, we cannot -continue to run this program." - -Checking some sort of condition and then `panic!`ing if something is wrong is a -common use of `panic!`. In fact, there's a second macro in Rust, `assert!` for -this case. `assert!` will check some kind of condition, and `panic!` if the -condition is false. We could also write our function like this: - -```rust -fn check_guess(number: u32) -> bool { - assert!(number < 100); - - number == 34 -} -``` - -If we try and use `check_guess` in a program, and make an error: - -```rust,should_panic -fn check_guess(number: u32) -> bool { - assert!(number < 100); - - number == 34 -} - -fn main() { - let answer = check_guess(5); - println!("answer was: {}", answer); - - let answer = check_guess(34); - println!("answer was: {}", answer); - - let answer = check_guess(500); - println!("answer was: {}", answer); -} -``` - -We'll see output like this: - -```text -answer was: false -answer was: true - -thread '
' panicked at 'assertion failed: number < 100', :2 -``` - -First, `5` was okay, but false. Then, `34` was okay, but true. Finally, `500` -caused a panic. - -Panics cause your program to stop executing. To check this, we could move the -failing case above the good cases: - -```rust,should_panic -# fn check_guess(number: u32) -> bool { -# assert!(number < 100); -# -# number == 34 -# } -# -fn main() { - let answer = check_guess(500); - println!("answer was: {}", answer); - - let answer = check_guess(5); - println!("answer was: {}", answer); - - let answer = check_guess(34); - println!("answer was: {}", answer); -} -``` - -If we run it, we'll see that we never check `5` or `34`: - -```text -thread '
' panicked at 'assertion failed: number < 100', :2 -``` - -This is why we call `panic!` an unrecoverable error: the other code never gets a -chance to run. Our program just ends. - -But what does it mean to "end a program"? As it turns out, there are multiple -strategies for processing an unrecoverable error. The two main ones are -'unwinding' and 'aborting'. - -## Unwinding - -By default, when a `panic!` happens in Rust, it starts doing something called -"unwinding". To explain unwinding, let's consider a slightly more complex -program: +silly function, but it's similar to a real example you've already seen: +indexing vectors: ```rust,should_panic -fn step1() { - let s = String::from("Step 1"); - step2(); -} - -fn step2() { - let s = String::from("Step 2"); - step3(); -} - -fn step3() { - let s = String::from("Step 3"); - check_guess(500); -} - -fn check_guess(number: u32) -> bool { - assert!(number < 100); +let v = vec![1, 2, 3]; - number == 34 -} - -fn main() { - step1(); -} +v[1000]; // this will panic ``` -Here, we have four functions total: `step1` calls `step2` which calls `step3` -which then calls `check_guess`. Something like this diagram, with each of the -variable bindings written in: - -```text -> main - | - > step1 String: "Step 1" - | - > step2 String: "Step 2" - | - > step3 String: "Step 3" - | - > check_guess: u32: 500 -``` +The implementation of indexing with `[]` looks similar to our `check_guess` +function above: check if the number is larger than the length of the vector, +and if it is, panic. -When `check_guess` causes a `panic!` via `assert!`, it will walk back through -each of these functions, and clean them up. We haven't yet talked about -destructors in Rust, that'll come in Chapter XX. For now, think about it this -way: simple values like that `u32` can be destroyed by freeing their memory. -More complicated values, like `String`s, have more complicated needs. In these -cases, there is a function that does this cleanup. We call this a "drop -function" in Rust. - -So, the first thing that will happen is that `check_guess`, our current -function, gets cleaned up. There's only one value, the `500`, and so its memory -will be freed. Now our program looks like this: - -```text -> main - | - > step1 String: "Step 1" - | - > step2 String: "Step 2" - | - > step3 String: "Step 3" -``` - -Now we're on `step3`. In the same way, Rust will call the `String`'s drop -function, deallocating the `String`. Once that's done, we can move on: - -```text -> main - | - > step1 String: "Step 1" - | - > step2 String: "Step 2" -``` - -The pattern continues: `step2` has a single value, and `"Step 2"` has its -drop function called. Next! - -```text -> main - | - > step1 String: "Step 1" -``` - -Almost done! `step1` also has a `String`, so Rust will invoke its drop function. - -```text -> main -``` - -Finally, all we have left is `main()`. It has no variable bindings or arguments -in this case, so there's nothing to clean up. Our whole program has been delt -with, and so terminates execution, after printing a message. - -## Aborting - -Doing all that is a lot of work! And the end result is that our program -terminates. Handing panics with unwinding is useful in many scenarios, but some -applications would rather skip straight to the end, and 'abort'. With some -configuration, Cargo will allow us to use this alternate implementation of -`panic!`. What happens when that's enabled? Let's consider what our call stack -looked like: - -```text -> main - | - > step1 String: "Step 1" - | - > step2 String: "Step 2" - | - > step3 String: "Step 3" - | - > check_guess: u32: 500 -``` +Why do we need to panic? There's no number type for "between zero and a +hundred" in Rust, so we are accepting a `u32`, and then checking in the +function's body to make sure the guess is in-bounds. If the number is too big, +there's been an error: someone made a mistake. We then invoke `panic!` to say +"something went wrong, we cannot continue to run this program." -With an abort implementation of `panic!`, instead of walking back up through the -previous functions and cleaning everything up, we skip straight to the end: our -program terminates. But what about our resources? In the case of memory, like is -the case with `String`s, the operating system will reclaim the memory. So for -this program, the two cases are identical, but aborting is much more efficient. -Other, more complex resources work differently, however, and so aborting may not -always be the right choice either. It depends! From e7b5943c355751a98f090b3744f40d32f4659f7b Mon Sep 17 00:00:00 2001 From: Steve Klabnik Date: Tue, 23 Aug 2016 14:51:45 -0400 Subject: [PATCH 03/53] error handling take two --- src/ch07-01-error-handling.md | 25 +-- ...ch07-02-unrecoverable-errors-with-panic.md | 153 +++++++++++-- src/ch07-03-recoverable-errors-with-result.md | 211 +++++++++++------- src/ch07-04-creating-your-own-error-types.md | 33 +++ 4 files changed, 305 insertions(+), 117 deletions(-) diff --git a/src/ch07-01-error-handling.md b/src/ch07-01-error-handling.md index 5defc4e8b7..f13720342d 100644 --- a/src/ch07-01-error-handling.md +++ b/src/ch07-01-error-handling.md @@ -5,26 +5,13 @@ Errors are a fact of life in software. Rust has a number of tools that you can use to handle things when something bad happens. Rust splits errors into two major kinds: errors that are recoverable, and -errors that are not recoverable. It has two different strategies to handle -these two different forms of errors. - -What does it mean to "recover" from an error? In the simplest sense, it -relates to the answer of this question: +errors that are not recoverable. What does it mean to "recover" from an +error? In the simplest sense, it relates to the answer of this question: > If I call a function, and something bad happens, can I do something > meaningful? Or should execution stop? -Some kinds of problems have solutions, but with other kinds of errors, -all you can do is throw up your hands and give up. - -We'll start off our examination of error handling by talking about the -unrecoverable case first. Why? You can think of unrecoverable errors as a -subset of recoverable errors. If you choose to treat an error as unrecoverable, -then the function that calls your code has no choice in the matter. However, if -your function returns a recoverable error, the calling function has a choice: -handle the error properly, or convert it into an unrecoverable error. This is -one of the reasons that most Rust code chooses to treat errors as recoverable: -it's more flexible. We're going to explore an example that starts off as -unrecoverable, and then, in the next section, we will convert it to a -convertable error. Finally, we'll talk about how to create our own custom error -types. +The technique that you use depends on the answer to this question. First, +we'll talk about `panic!`, Rust's way of signaling an unrecoverable error. +Then, we'll talk about `Result`, the return type for functions that +may return an error, but one you can recover from. diff --git a/src/ch07-02-unrecoverable-errors-with-panic.md b/src/ch07-02-unrecoverable-errors-with-panic.md index 5fdea323af..786c1d1b78 100644 --- a/src/ch07-02-unrecoverable-errors-with-panic.md +++ b/src/ch07-02-unrecoverable-errors-with-panic.md @@ -1,36 +1,143 @@ # Unrecoverable errors with panic! -You've already seen the way to signal an unrecoverable error: the `panic!` -macro. Here's an example of using `panic!`: +Sometimes, bad things happen, and there's nothing that you can do about it. For +these cases, Rust has a macro, `panic!`. When this macro executes, your program +will terminate execution, printing a failure message and then quitting. Try +this program: -```rust -fn check_guess(number: u32) -> bool { - if number > 100 { - panic!("Guess was too big: {}", number); - } - - number == 34 +```rust,should_panic +fn main() { + panic!("crash and burn"); } ``` -This function accepts a guess between zero and a hundred, and checks if it's -equivalent to the correct number, which is `34` in this case. It's kind of a -silly function, but it's similar to a real example you've already seen: -indexing vectors: +If you run it, you'll see something like this: + +```bash +$ cargo run + Compiling panic v0.1.0 (file:///home/steve/tmp/panic) + Finished debug [unoptimized + debuginfo] target(s) in 0.25 secs + Running `target/debug/panic` +thread 'main' panicked at 'crash and burn', src/main.rs:2 +note: Run with `RUST_BACKTRACE=1` for a backtrace. +error: Process didn't exit successfully: `target/debug/panic` (exit code: 101) +``` + +There are three lines of error message here. The first line shows our panic +message and the place in our source code where the panic occurred: +`src/main.rs`, line two. + +But that only shows us the exact line that called `panic!`. That's not always +useful. Let's modify our example slightly: ```rust,should_panic -let v = vec![1, 2, 3]; +fn main() { + let v = vec![1, 2, 3]; + + v[100]; +} +``` + +We attempt to access the hundredth element of our vector, but it only has three +elements. In this situation, Rust will panic. Let's try it: + +```bash +$ cargo run + Compiling panic v0.1.0 (file:///home/steve/tmp/panic) + Finished debug [unoptimized + debuginfo] target(s) in 0.27 secs + Running `target/debug/panic` +thread 'main' panicked at 'index out of bounds: the len is 3 but the index is +100', ../src/libcollections/vec.rs:1265 +note: Run with `RUST_BACKTRACE=1` for a backtrace. +error: Process didn't exit successfully: `target/debug/panic` (exit code: 101) +``` -v[1000]; // this will panic +This points at a file we didn't write, `../src/libcollections/vec.rs`, line 1265. +That's the implementation of `Vec` in the standard library. While it's easy +to see in this short program where the error was, it would be nicer if we could +have Rust tell us what line in our program caused the error. + +That's what the next line, the `note` is about. If we set the `RUST_BACKTRACE` +environment variable, we'll get a backtrace of exactly how the error happend. +Let's try it: + +```bash +$ RUST_BACKTRACE=1 cargo run + Finished debug [unoptimized + debuginfo] target(s) in 0.0 secs + Running `target/debug/panic` +thread 'main' panicked at 'index out of bounds: the len is 3 but the index is +100', ../src/libcollections/vec.rs:1265 +stack backtrace: + 1: 0x560956150ae9 - +std::sys::backtrace::tracing::imp::write::h482d45d91246faa2 + 2: 0x56095615345c - +std::panicking::default_hook::_{{closure}}::h89158f66286b674e + 3: 0x56095615291e - std::panicking::default_hook::h9e30d428ee3b0c43 + 4: 0x560956152f88 - +std::panicking::rust_panic_with_hook::h2224f33fb7bf2f4c + 5: 0x560956152e22 - std::panicking::begin_panic::hcb11a4dc6d779ae5 + 6: 0x560956152d50 - std::panicking::begin_panic_fmt::h310416c62f3935b3 + 7: 0x560956152cd1 - rust_begin_unwind + 8: 0x560956188a2f - core::panicking::panic_fmt::hc5789f4e80194729 + 9: 0x5609561889d3 - +core::panicking::panic_bounds_check::hb2d969c3cc11ed08 + 10: 0x56095614c075 - _ as +core..ops..Index>::index::hb9f10d3dadbe8101 + at ../src/libcollections/vec.rs:1265 + 11: 0x56095614c134 - panic::main::h2d7d3751fb8705e2 + at /home/steve/tmp/panic/src/main.rs:4 + 12: 0x56095615af46 - __rust_maybe_catch_panic + 13: 0x560956152082 - std::rt::lang_start::h352a66f5026f54bd + 14: 0x56095614c1b3 - main + 15: 0x7f75b88ed72f - __libc_start_main + 16: 0x56095614b3c8 - _start + 17: 0x0 - +error: Process didn't exit successfully: `target/debug/panic` (exit code: 101) ``` -The implementation of indexing with `[]` looks similar to our `check_guess` -function above: check if the number is larger than the length of the vector, -and if it is, panic. +That's a lot of output! Line `11` there has the line in our project: +`src/main.rs` line four. We've been looking at the error message, but Cargo +also told us something important about backtraces early on: `[unoptimized + +debuginfo]`. 'debuginfo' is what enables the file names to be shown here. +If we instead compile with `--release`: + +```bash +$ RUST_BACKTRACE=1 cargo run --release + Compiling panic v0.1.0 (file:///home/steve/tmp/panic) + Finished release [optimized] target(s) in 0.28 secs + Running `target/release/panic` +thread 'main' panicked at 'index out of bounds: the len is 3 but the index is +100', ../src/libcollections/vec.rs:1265 +stack backtrace: + 1: 0x565238fd0e79 - +std::sys::backtrace::tracing::imp::write::h482d45d91246faa2 + 2: 0x565238fd37ec - +std::panicking::default_hook::_{{closure}}::h89158f66286b674e + 3: 0x565238fd2cae - std::panicking::default_hook::h9e30d428ee3b0c43 + 4: 0x565238fd3318 - +std::panicking::rust_panic_with_hook::h2224f33fb7bf2f4c + 5: 0x565238fd31b2 - std::panicking::begin_panic::hcb11a4dc6d779ae5 + 6: 0x565238fd30e0 - std::panicking::begin_panic_fmt::h310416c62f3935b3 + 7: 0x565238fd3061 - rust_begin_unwind + 8: 0x565239008dbf - core::panicking::panic_fmt::hc5789f4e80194729 + 9: 0x565239008d63 - +core::panicking::panic_bounds_check::hb2d969c3cc11ed08 + 10: 0x565238fcc526 - panic::main::h2d7d3751fb8705e2 + 11: 0x565238fdb2d6 - __rust_maybe_catch_panic + 12: 0x565238fd2412 - std::rt::lang_start::h352a66f5026f54bd + 13: 0x7f36aad6372f - __libc_start_main + 14: 0x565238fcc408 - _start + 15: 0x0 - +error: Process didn't exit successfully: `target/release/panic` (exit code: +101) +``` -Why do we need to panic? There's no number type for "between zero and a -hundred" in Rust, so we are accepting a `u32`, and then checking in the -function's body to make sure the guess is in-bounds. If the number is too big, -there's been an error: someone made a mistake. We then invoke `panic!` to say -"something went wrong, we cannot continue to run this program." +Now it just says 'optimized', and we don't have the file names any more. These +settings are only the default; you can include debuginfo in a release build, +or exclude it from a debug build, by configuring Cargo. See its documentation +for more details: http://doc.crates.io/manifest.html#the-profile-sections +So why does Rust panic here? In this case, using `[]` is supposed to return +a number. But if you pass it an invalid index, there's no number Rust could +return here, it would be wrong. So the only thing that we can do is terminate +the program. diff --git a/src/ch07-03-recoverable-errors-with-result.md b/src/ch07-03-recoverable-errors-with-result.md index d341ae7ce6..9b63e4a34a 100644 --- a/src/ch07-03-recoverable-errors-with-result.md +++ b/src/ch07-03-recoverable-errors-with-result.md @@ -1,116 +1,177 @@ # Recoverable errors with `Result` -The vast majority of errors in Rust are able to be recovered from. For this -case, Rust has a special type, `Result`, to signal that a function might -succeed or fail. +Most errors aren't so dire. When a function can fail for some reason, it'd be +nice for it to not kill our entire program. As an example, maybe we are making +a request to a website, but it's down for maintenance. In this situation, we'd +like to wait and then try again. Terminating our process isn't the right thing +to do here. -Let's take a look at our example function from the last section: +In these cases, Rust's standard library provides an `enum` to use as the return +type of the function: ```rust -fn check_guess(number: u32) -> bool { - if number > 100 { - panic!("Guess was too big: {}", number); - } - - number == 34 +enum Result { + Ok(T), + Err(E), } ``` -We don't want the entire program to end if our `number` was incorrect. That's a -bit harsh! Instead, we want to signal that an error occurred. Here's a version -of `check_guess` that uses `Result`: +We have `Ok` for successful results, and `Err` for ones that have an error. +These two variants each contain one thing: in `Ok`'s case, it's the successful +return value. With `Err`, it's some type that represents the error. + +As an example, let's try opening a file: ```rust -fn check_guess(number: u32) -> Result { - if number > 100 { - return Err("Number was out of range"); - } +use std::fs::File; - Ok(number == 34) +fn main() { + let f = File::open("hello.txt"); } ``` -There are three big changes here: to the return type, to the error case, and to -the non-error case. Let's look at each in turn. +The `open` function returns a `Result`: there are many ways in which opening +a file can fail. For example, unless we created `hello.txt`, this file does +not yet exist. Before we can do anything with our `File`, we need to extract +it out of the result. Let's start with a basic tool: `match`. We've used it +to deal with enums previously. -```rust -fn check_guess(number: u32) -> Result { -# if number > 100 { -# return Err("Number was out of range"); -# } -# -# Ok(number == 34) -# } +```rust,should_panic +use std::fs::File; + +fn main() { + let f = File::open("hello.txt"); + + let f = match f { + Ok(file) => file, + Err(error) => panic!("There was a problem opening the file: {:?}", +error), + }; +} ``` -Originally, we returned a `bool`, but now we return a -`Result`. This is a type [provided by the standard library] -specifically for indicating that a function might have an error. More -specifically, it's an [`enum`] that looks like this: +If we see an `Ok`, we can return the inner `file` out of it. If we see `Err`, +we have to decide what to do with it. The simplest thing is to turn our error +into a `panic!` instead, by calling the macro. And since we haven't created +that file yet, we'll see it in the error message: -```rust -pub enum Result { - Ok(T), - Err(E), +```text +thread 'main' panicked at 'There was a problem opening the file: Error { repr: +Os { code: 2, message: "No such file or directory" } }', src/main.rs:8 +``` + +This works okay. However, `match` can be a bit verbose, and it doesn't always +communicate intent well. The `Result` type has many helper methods +defined it to do various things. "Panic on an error result" is one of those +methods, and it's called `unwrap`: + +```rust,should_panic +use std::fs::File; + +fn main() { + let f = File::open("hello.txt").unwrap(); } ``` -[provided by the standard library]: https://doc.rust-lang.org/stable/std/result/enum.Result.html -[`enum]`: ch06-01-enums.html +This has the same behavior as our previous example: If the call to `open` +returns `Ok`, return the value inside. If it's an `Err`, panic. + +This isn't the only way to deal with errors, however. This entire section is +supposed to be about recovering from errors, but we've gone back to panic. +This is true, and gets at an underlying truth: you can easily turn a +recoverable error into an unrecoverable one with `unwrap`, but you can't turn +an unrecoverable error into a recoverable one. This is why good Rust code +chooses to make errors recoverable: you give your caller options. + +The Rust community has a love/hate relationship with `unwrap`. It's useful +in tests, and in examples where you don't want to muddy the example with proper +error handling. But if used in library code, mis-using that library can cause +your program to blow up, and that's not good. + +## Propagating errors with `?` -`Result` is generic over two types: `T`, which is the successful case, and -`E`, which is the error case. It has two variants, `Ok` and `Err`, which also -correspond to these cases, respectively. So the type `Result` means that in the successful, `Ok` case, we will be returning a `bool`. -But in the failure, `Err` case, we will be returning a string literal. +Sometimes, when writing a function, you don't want to handle the error where +you are, but instead would prefer to return the error to the calling function. +Something like this: ```rust -# fn check_guess(number: u32) -> Result { -# if number > 100 { - return Err("Number was out of range"); -# } -# -# Ok(number == 34) +use std::fs::File; + +# fn foo() -> std::io::Result<()> { +let f = File::open("hello.txt"); + +let f = match f { + Ok(file) => file, + Err(e) => return Err(e), +}; + +# Ok(()) # } ``` -The second change we need to make is to our error case. Instead of causing a -`panic!`, we now `return` an `Err`, with a string literal inside. Remember, -`Result` is an enum: `Err` is one of its variants. +This is a very common way of handling errors: propagate them upward until +you're ready to deal with them. This pattern is so common in Rust that there is +dedicated syntax for it: the question mark operator. We could have also written +the example like this: ```rust -# fn check_guess(number: u32) -> Result { -# if number > 100 { -# return Err("Number was out of range"); -# } -# - Ok(number == 34) +#![feature(question_mark)] + +use std::fs::File; + +# fn foo() -> std::io::Result<()> { +let f = File::open("hello.txt")?; +# Ok(()) # } ``` -We also need to handle the successful case as well, and we make a change -similarly to the error case: we wrap our return value in `Ok`, which gives it -the correct type. - -## Handling an error +The `?` operator at the end of the `open` call does the same thing as our +previous example: It will return the value of an `Ok`, but return the value of +an `Err` to our caller. + +There's one problem though: let's try compiling the example: + +```rust,ignore + Compiling result v0.1.0 (file:///home/steve/tmp/result) +error[E0308]: mismatched types + --> src/main.rs:6:13 + | +6 | let f = File::open("hello.txt")?; + | ^^^^^^^^^^^^^^^^^^^^^^^^ expected (), found enum +`std::result::Result` + | + = note: expected type `()` + = note: found type `std::result::Result<_, _>` + +error: aborting due to previous error +``` -Let's examine how to use this function: +What gives? The issue is that the `main()` function has a return type of `()`, +but the question mark operator is trying to return a result. This doesn't work. +Instead of `main()`, let's create a function that returns a result: ```rust -fn check_guess(number: u32) -> Result { - if number > 100 { - return Err("Number was out of range"); - } +#![feature(question_mark)] - Ok(number == 34) -} +use std::fs::File; +use std::io; fn main() { - let answer = check_guess(5); +} - match answer { - Ok(b) => println!("answer: {}", b), - Err(e) => println!("There was an error: {}, e"), - }; +pub fn process_file() -> Result<(), io::Error> { + let f = File::open("hello.txt")?; + + // do some stuff with f + + Ok(()) } ``` + +Since the result type has two type parameters, we need to include them. In this +case, `File::open` returns an `std::io::Error`, so we will use it as our error +type. But what about success? This function is executed purely for its side +effects; nothing is returned upon success. Well, functions with no return type, +as we just saw with `main()`, are the same as returning unit. So we can use +it as the return type here, too. This leads to the last line of the function, +the slightly silly-looking `Ok(())`. This is an `Ok()` with a `()` inside. diff --git a/src/ch07-04-creating-your-own-error-types.md b/src/ch07-04-creating-your-own-error-types.md index 102abe0d75..0e56945db4 100644 --- a/src/ch07-04-creating-your-own-error-types.md +++ b/src/ch07-04-creating-your-own-error-types.md @@ -1 +1,34 @@ # Creating your own Error types + +This pattern of "return an error" is so common, many libraries create their +own error type, and use it for all of their functions. We can re-write the +previous example to use `std::io::Result` rathern than a regular `Result`: + +```rust +#![feature(question_mark)] + +use std::fs::File; +use std::io; + +fn main() { +} + +pub fn process_file() -> io::Result<()> { + let f = File::open("hello.txt")?; + + // do some stuff with f + + Ok(()) +} +``` + +`io::Result` looks like this: + +```rust +# use std::io; +type Result = Result; +``` + +It's a type alias for a regular-old `Result`, with the `E` set up to be a +`std::io::Error`. This means we don't need to worry about the error type in our +function signatures, which is nice. From e35e0e1039bd1ad2bdc0de9ec1e45568d11cd4ca Mon Sep 17 00:00:00 2001 From: Steve Klabnik Date: Thu, 25 Aug 2016 16:30:50 -0400 Subject: [PATCH 04/53] Remove 'creating your own error types' this should end up later in the book, someday --- src/SUMMARY.md | 1 - src/ch07-03-recoverable-errors-with-result.md | 3 ++ src/ch07-04-creating-your-own-error-types.md | 34 ------------------- 3 files changed, 3 insertions(+), 35 deletions(-) delete mode 100644 src/ch07-04-creating-your-own-error-types.md diff --git a/src/SUMMARY.md b/src/SUMMARY.md index 2583ec7179..1eb7961b12 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -6,7 +6,6 @@ - [Guessing Game Tutorial](ch02-00-guessing-game-tutorial.md) -<<<<<<< a2bc57618340b6257b9a4dd7d4f3993c71cde2e4 - [Common Programming Concepts in Rust](ch03-00-common-programming-concepts-in-rust.md) - [Variable Bindings and Mutability](ch03-01-variable-bindings-and-mutability.md) - [Data Types](ch03-02-data-types.md) diff --git a/src/ch07-03-recoverable-errors-with-result.md b/src/ch07-03-recoverable-errors-with-result.md index 9b63e4a34a..30ef1a3f8d 100644 --- a/src/ch07-03-recoverable-errors-with-result.md +++ b/src/ch07-03-recoverable-errors-with-result.md @@ -175,3 +175,6 @@ effects; nothing is returned upon success. Well, functions with no return type, as we just saw with `main()`, are the same as returning unit. So we can use it as the return type here, too. This leads to the last line of the function, the slightly silly-looking `Ok(())`. This is an `Ok()` with a `()` inside. + +In chapter XX, we'll learn how to make our own types like these, but for now, +an understanding of the core `Result` is enough. diff --git a/src/ch07-04-creating-your-own-error-types.md b/src/ch07-04-creating-your-own-error-types.md deleted file mode 100644 index 0e56945db4..0000000000 --- a/src/ch07-04-creating-your-own-error-types.md +++ /dev/null @@ -1,34 +0,0 @@ -# Creating your own Error types - -This pattern of "return an error" is so common, many libraries create their -own error type, and use it for all of their functions. We can re-write the -previous example to use `std::io::Result` rathern than a regular `Result`: - -```rust -#![feature(question_mark)] - -use std::fs::File; -use std::io; - -fn main() { -} - -pub fn process_file() -> io::Result<()> { - let f = File::open("hello.txt")?; - - // do some stuff with f - - Ok(()) -} -``` - -`io::Result` looks like this: - -```rust -# use std::io; -type Result = Result; -``` - -It's a type alias for a regular-old `Result`, with the `E` set up to be a -`std::io::Error`. This means we don't need to worry about the error type in our -function signatures, which is nice. From 80c97251b8bce8c18ca12eac3dc9b0981fd2bbb9 Mon Sep 17 00:00:00 2001 From: Steve Klabnik Date: Thu, 25 Aug 2016 16:31:50 -0400 Subject: [PATCH 05/53] remove second backtrace --- ...ch07-02-unrecoverable-errors-with-panic.md | 41 +------------------ 1 file changed, 1 insertion(+), 40 deletions(-) diff --git a/src/ch07-02-unrecoverable-errors-with-panic.md b/src/ch07-02-unrecoverable-errors-with-panic.md index 786c1d1b78..fa045a5fb1 100644 --- a/src/ch07-02-unrecoverable-errors-with-panic.md +++ b/src/ch07-02-unrecoverable-errors-with-panic.md @@ -96,46 +96,7 @@ error: Process didn't exit successfully: `target/debug/panic` (exit code: 101) ``` That's a lot of output! Line `11` there has the line in our project: -`src/main.rs` line four. We've been looking at the error message, but Cargo -also told us something important about backtraces early on: `[unoptimized + -debuginfo]`. 'debuginfo' is what enables the file names to be shown here. -If we instead compile with `--release`: - -```bash -$ RUST_BACKTRACE=1 cargo run --release - Compiling panic v0.1.0 (file:///home/steve/tmp/panic) - Finished release [optimized] target(s) in 0.28 secs - Running `target/release/panic` -thread 'main' panicked at 'index out of bounds: the len is 3 but the index is -100', ../src/libcollections/vec.rs:1265 -stack backtrace: - 1: 0x565238fd0e79 - -std::sys::backtrace::tracing::imp::write::h482d45d91246faa2 - 2: 0x565238fd37ec - -std::panicking::default_hook::_{{closure}}::h89158f66286b674e - 3: 0x565238fd2cae - std::panicking::default_hook::h9e30d428ee3b0c43 - 4: 0x565238fd3318 - -std::panicking::rust_panic_with_hook::h2224f33fb7bf2f4c - 5: 0x565238fd31b2 - std::panicking::begin_panic::hcb11a4dc6d779ae5 - 6: 0x565238fd30e0 - std::panicking::begin_panic_fmt::h310416c62f3935b3 - 7: 0x565238fd3061 - rust_begin_unwind - 8: 0x565239008dbf - core::panicking::panic_fmt::hc5789f4e80194729 - 9: 0x565239008d63 - -core::panicking::panic_bounds_check::hb2d969c3cc11ed08 - 10: 0x565238fcc526 - panic::main::h2d7d3751fb8705e2 - 11: 0x565238fdb2d6 - __rust_maybe_catch_panic - 12: 0x565238fd2412 - std::rt::lang_start::h352a66f5026f54bd - 13: 0x7f36aad6372f - __libc_start_main - 14: 0x565238fcc408 - _start - 15: 0x0 - -error: Process didn't exit successfully: `target/release/panic` (exit code: -101) -``` - -Now it just says 'optimized', and we don't have the file names any more. These -settings are only the default; you can include debuginfo in a release build, -or exclude it from a debug build, by configuring Cargo. See its documentation -for more details: http://doc.crates.io/manifest.html#the-profile-sections +`src/main.rs` line four. So why does Rust panic here? In this case, using `[]` is supposed to return a number. But if you pass it an invalid index, there's no number Rust could From 730fb1f82f0b3d33b2c09e45a66b6f84828130b7 Mon Sep 17 00:00:00 2001 From: Steve Klabnik Date: Thu, 25 Aug 2016 16:32:36 -0400 Subject: [PATCH 06/53] Move explanation up as per @aturon --- src/ch07-02-unrecoverable-errors-with-panic.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/ch07-02-unrecoverable-errors-with-panic.md b/src/ch07-02-unrecoverable-errors-with-panic.md index fa045a5fb1..c966eea23c 100644 --- a/src/ch07-02-unrecoverable-errors-with-panic.md +++ b/src/ch07-02-unrecoverable-errors-with-panic.md @@ -39,7 +39,12 @@ fn main() { ``` We attempt to access the hundredth element of our vector, but it only has three -elements. In this situation, Rust will panic. Let's try it: +elements. In this situation, Rust will panic. Using `[]` is supposed to return +a number. But if you pass it an invalid index, there's no number Rust could +return here, it would be wrong. So the only thing that we can do is terminate +the program. + +Let's try to run it: ```bash $ cargo run @@ -97,8 +102,3 @@ error: Process didn't exit successfully: `target/debug/panic` (exit code: 101) That's a lot of output! Line `11` there has the line in our project: `src/main.rs` line four. - -So why does Rust panic here? In this case, using `[]` is supposed to return -a number. But if you pass it an invalid index, there's no number Rust could -return here, it would be wrong. So the only thing that we can do is terminate -the program. From 3634c81fc74dd5c2c04b7c0e465d13aa6389eeb5 Mon Sep 17 00:00:00 2001 From: Steve Klabnik Date: Thu, 25 Aug 2016 16:33:26 -0400 Subject: [PATCH 07/53] address some feedback --- src/ch07-03-recoverable-errors-with-result.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/ch07-03-recoverable-errors-with-result.md b/src/ch07-03-recoverable-errors-with-result.md index 30ef1a3f8d..8d98cb2bca 100644 --- a/src/ch07-03-recoverable-errors-with-result.md +++ b/src/ch07-03-recoverable-errors-with-result.md @@ -1,10 +1,10 @@ # Recoverable errors with `Result` -Most errors aren't so dire. When a function can fail for some reason, it'd be -nice for it to not kill our entire program. As an example, maybe we are making -a request to a website, but it's down for maintenance. In this situation, we'd -like to wait and then try again. Terminating our process isn't the right thing -to do here. +Most errors aren't so dire. Sometimes, when a function fails, it's for a reason +that we can easily interpret and respond to. As an example, maybe we are +making a request to a website, but it's down for maintenance. In this +situation, we'd like to wait and then try again. Terminating our process isn't +the right thing to do here. In these cases, Rust's standard library provides an `enum` to use as the return type of the function: From ad0fcf33a5b22c303b5fb4b3da67919008f1e17a Mon Sep 17 00:00:00 2001 From: Steve Klabnik Date: Thu, 25 Aug 2016 16:37:43 -0400 Subject: [PATCH 08/53] It's not always possible to handle this at compile time --- src/ch07-02-unrecoverable-errors-with-panic.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/ch07-02-unrecoverable-errors-with-panic.md b/src/ch07-02-unrecoverable-errors-with-panic.md index c966eea23c..c690808ced 100644 --- a/src/ch07-02-unrecoverable-errors-with-panic.md +++ b/src/ch07-02-unrecoverable-errors-with-panic.md @@ -41,8 +41,11 @@ fn main() { We attempt to access the hundredth element of our vector, but it only has three elements. In this situation, Rust will panic. Using `[]` is supposed to return a number. But if you pass it an invalid index, there's no number Rust could -return here, it would be wrong. So the only thing that we can do is terminate -the program. +return here, it would be wrong. In this case, we've typed in a literal, so in +theory, Rust could figure it out. But if our program was different, say, maybe +reading the index from user input, it would not be possible to determine at +compile time if the index was in bounds. So the only thing that we can do is +terminate the program. Let's try to run it: From 0b7bb54df41ac0e22d01fb9455911c04cc1a187b Mon Sep 17 00:00:00 2001 From: Steve Klabnik Date: Fri, 26 Aug 2016 13:39:27 -0400 Subject: [PATCH 09/53] some more small things --- src/ch07-02-unrecoverable-errors-with-panic.md | 7 +++++-- src/ch07-03-recoverable-errors-with-result.md | 14 +++++++++++--- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/src/ch07-02-unrecoverable-errors-with-panic.md b/src/ch07-02-unrecoverable-errors-with-panic.md index c690808ced..9d5bc620f1 100644 --- a/src/ch07-02-unrecoverable-errors-with-panic.md +++ b/src/ch07-02-unrecoverable-errors-with-panic.md @@ -2,8 +2,11 @@ Sometimes, bad things happen, and there's nothing that you can do about it. For these cases, Rust has a macro, `panic!`. When this macro executes, your program -will terminate execution, printing a failure message and then quitting. Try -this program: +will terminate execution, printing a failure message and then quitting. The most +common reason for this is when a bug of some kind has been detected, and it's not +clear how to handle the error. + +Let's try it out with a simple program: ```rust,should_panic fn main() { diff --git a/src/ch07-03-recoverable-errors-with-result.md b/src/ch07-03-recoverable-errors-with-result.md index 8d98cb2bca..1811ee9e43 100644 --- a/src/ch07-03-recoverable-errors-with-result.md +++ b/src/ch07-03-recoverable-errors-with-result.md @@ -76,6 +76,17 @@ fn main() { This has the same behavior as our previous example: If the call to `open` returns `Ok`, return the value inside. If it's an `Err`, panic. +There's also another method, similar to unwrap, that lets us choose the error +message: `expect`. It looks like this: + +```rust,should_panic +use std::fs::File; + +fn main() { + let f = File::open("hello.txt").expect("failed to open hello.txt"); +} +``` + This isn't the only way to deal with errors, however. This entire section is supposed to be about recovering from errors, but we've gone back to panic. This is true, and gets at an underlying truth: you can easily turn a @@ -156,9 +167,6 @@ Instead of `main()`, let's create a function that returns a result: use std::fs::File; use std::io; -fn main() { -} - pub fn process_file() -> Result<(), io::Error> { let f = File::open("hello.txt")?; From 71d4ab96d4fb36aed54367e594f329d3c941077c Mon Sep 17 00:00:00 2001 From: Steve Klabnik Date: Fri, 26 Aug 2016 13:40:58 -0400 Subject: [PATCH 10/53] disable non-nightly build --- .travis.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index dd89cceeb0..1e0a1d66cb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,7 +3,10 @@ language: rust cache: cargo rust: - nightly - - stable +# - beta +# ^ to be changed once a new beta is cut that doesn't crash +# - stable +# ^ to be changed once question_mark becomes stable before_script: - (cargo install mdbook || true) script: From bd18fff64eb7250dffa569dfdf7d336b705a8070 Mon Sep 17 00:00:00 2001 From: Steve Klabnik Date: Fri, 26 Aug 2016 13:54:57 -0400 Subject: [PATCH 11/53] don't put my dir in output names --- src/ch07-02-unrecoverable-errors-with-panic.md | 6 +++--- src/ch07-03-recoverable-errors-with-result.md | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/ch07-02-unrecoverable-errors-with-panic.md b/src/ch07-02-unrecoverable-errors-with-panic.md index 9d5bc620f1..443c0c2ccc 100644 --- a/src/ch07-02-unrecoverable-errors-with-panic.md +++ b/src/ch07-02-unrecoverable-errors-with-panic.md @@ -18,7 +18,7 @@ If you run it, you'll see something like this: ```bash $ cargo run - Compiling panic v0.1.0 (file:///home/steve/tmp/panic) + Compiling panic v0.1.0 (file:///projects/panic) Finished debug [unoptimized + debuginfo] target(s) in 0.25 secs Running `target/debug/panic` thread 'main' panicked at 'crash and burn', src/main.rs:2 @@ -54,7 +54,7 @@ Let's try to run it: ```bash $ cargo run - Compiling panic v0.1.0 (file:///home/steve/tmp/panic) + Compiling panic v0.1.0 (file:///projects/panic) Finished debug [unoptimized + debuginfo] target(s) in 0.27 secs Running `target/debug/panic` thread 'main' panicked at 'index out of bounds: the len is 3 but the index is @@ -96,7 +96,7 @@ core::panicking::panic_bounds_check::hb2d969c3cc11ed08 core..ops..Index>::index::hb9f10d3dadbe8101 at ../src/libcollections/vec.rs:1265 11: 0x56095614c134 - panic::main::h2d7d3751fb8705e2 - at /home/steve/tmp/panic/src/main.rs:4 + at /projects/panic/src/main.rs:4 12: 0x56095615af46 - __rust_maybe_catch_panic 13: 0x560956152082 - std::rt::lang_start::h352a66f5026f54bd 14: 0x56095614c1b3 - main diff --git a/src/ch07-03-recoverable-errors-with-result.md b/src/ch07-03-recoverable-errors-with-result.md index 1811ee9e43..9c60f37115 100644 --- a/src/ch07-03-recoverable-errors-with-result.md +++ b/src/ch07-03-recoverable-errors-with-result.md @@ -143,7 +143,7 @@ an `Err` to our caller. There's one problem though: let's try compiling the example: ```rust,ignore - Compiling result v0.1.0 (file:///home/steve/tmp/result) + Compiling result v0.1.0 (file:///projects/result) error[E0308]: mismatched types --> src/main.rs:6:13 | From 6f3cf5c84c59381b894713c924c8fa8a04f9160e Mon Sep 17 00:00:00 2001 From: "Carol (Nichols || Goulding)" Date: Wed, 31 Aug 2016 11:05:58 -0400 Subject: [PATCH 12/53] Rename files since we decided errors is chapter 9 --- src/SUMMARY.md | 6 +++--- ...{ch07-01-error-handling.md => ch09-00-error-handling.md} | 0 ...-panic.md => ch09-01-unrecoverable-errors-with-panic.md} | 0 ...-result.md => ch09-02-recoverable-errors-with-result.md} | 0 4 files changed, 3 insertions(+), 3 deletions(-) rename src/{ch07-01-error-handling.md => ch09-00-error-handling.md} (100%) rename src/{ch07-02-unrecoverable-errors-with-panic.md => ch09-01-unrecoverable-errors-with-panic.md} (100%) rename src/{ch07-03-recoverable-errors-with-result.md => ch09-02-recoverable-errors-with-result.md} (100%) diff --git a/src/SUMMARY.md b/src/SUMMARY.md index 1eb7961b12..2cd19129e8 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -36,9 +36,9 @@ - [Strings]() - [`HashMap`]() -- [Error Handling](ch07-01-error-handling.md) - - [Unrecoverable errors with panic!](ch07-02-unrecoverable-errors-with-panic.md) - - [Recoverable errors with `Result`](ch07-03-recoverable-errors-with-result.md) +- [Error Handling](ch09-00-error-handling.md) + - [Unrecoverable errors with panic!](ch09-01-unrecoverable-errors-with-panic.md) + - [Recoverable errors with `Result`](ch09-02-recoverable-errors-with-result.md) - [Lifetimes]() diff --git a/src/ch07-01-error-handling.md b/src/ch09-00-error-handling.md similarity index 100% rename from src/ch07-01-error-handling.md rename to src/ch09-00-error-handling.md diff --git a/src/ch07-02-unrecoverable-errors-with-panic.md b/src/ch09-01-unrecoverable-errors-with-panic.md similarity index 100% rename from src/ch07-02-unrecoverable-errors-with-panic.md rename to src/ch09-01-unrecoverable-errors-with-panic.md diff --git a/src/ch07-03-recoverable-errors-with-result.md b/src/ch09-02-recoverable-errors-with-result.md similarity index 100% rename from src/ch07-03-recoverable-errors-with-result.md rename to src/ch09-02-recoverable-errors-with-result.md From 9057190e0dffde0848f7ef2ccbeeb401eeea0ee6 Mon Sep 17 00:00:00 2001 From: "Carol (Nichols || Goulding)" Date: Wed, 31 Aug 2016 11:57:52 -0400 Subject: [PATCH 13/53] Wording tweaks --- src/ch09-00-error-handling.md | 10 +++++++--- src/ch09-01-unrecoverable-errors-with-panic.md | 10 +++++----- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/src/ch09-00-error-handling.md b/src/ch09-00-error-handling.md index f13720342d..4b00740dd1 100644 --- a/src/ch09-00-error-handling.md +++ b/src/ch09-00-error-handling.md @@ -1,8 +1,12 @@ # Error Handling Rust's laser-focus on safety spills over into a related area: error handling. -Errors are a fact of life in software. Rust has a number of tools that you can -use to handle things when something bad happens. +Errors are a fact of life in software, so Rust has a number of tools that you +can use to handle situations in which something bad happens. In many cases, +Rust requires you to acknowledge the possibility of an error occurring and take +some action in that situation. This makes your program safer and more robust by +eliminating the possibility of unexpected errors being discovered late in the +development process. Rust splits errors into two major kinds: errors that are recoverable, and errors that are not recoverable. What does it mean to "recover" from an @@ -14,4 +18,4 @@ error? In the simplest sense, it relates to the answer of this question: The technique that you use depends on the answer to this question. First, we'll talk about `panic!`, Rust's way of signaling an unrecoverable error. Then, we'll talk about `Result`, the return type for functions that -may return an error, but one you can recover from. +may return an error you can recover from. diff --git a/src/ch09-01-unrecoverable-errors-with-panic.md b/src/ch09-01-unrecoverable-errors-with-panic.md index 443c0c2ccc..36f0ac506c 100644 --- a/src/ch09-01-unrecoverable-errors-with-panic.md +++ b/src/ch09-01-unrecoverable-errors-with-panic.md @@ -2,11 +2,11 @@ Sometimes, bad things happen, and there's nothing that you can do about it. For these cases, Rust has a macro, `panic!`. When this macro executes, your program -will terminate execution, printing a failure message and then quitting. The most -common reason for this is when a bug of some kind has been detected, and it's not -clear how to handle the error. +will print a failure message and then quit. The most common reason for this is +when a bug of some kind has been detected, and it's not clear how to handle the +error. -Let's try it out with a simple program: +Let's try it out with a simple program: ```rust,should_panic fn main() { @@ -43,7 +43,7 @@ fn main() { We attempt to access the hundredth element of our vector, but it only has three elements. In this situation, Rust will panic. Using `[]` is supposed to return -a number. But if you pass it an invalid index, there's no number Rust could +an element. But if you pass it an invalid index, there's no number Rust could return here, it would be wrong. In this case, we've typed in a literal, so in theory, Rust could figure it out. But if our program was different, say, maybe reading the index from user input, it would not be possible to determine at From 3f9dd9dfa1fb22403968e8ffd6aacdb9a99b2a4a Mon Sep 17 00:00:00 2001 From: "Carol (Nichols || Goulding)" Date: Wed, 31 Aug 2016 17:25:23 -0400 Subject: [PATCH 14/53] Explain the code we're going to try a bit more before showing it --- src/ch09-01-unrecoverable-errors-with-panic.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/ch09-01-unrecoverable-errors-with-panic.md b/src/ch09-01-unrecoverable-errors-with-panic.md index 36f0ac506c..ec393613e9 100644 --- a/src/ch09-01-unrecoverable-errors-with-panic.md +++ b/src/ch09-01-unrecoverable-errors-with-panic.md @@ -31,7 +31,8 @@ message and the place in our source code where the panic occurred: `src/main.rs`, line two. But that only shows us the exact line that called `panic!`. That's not always -useful. Let's modify our example slightly: +useful. Let's modify our example slightly to see what it's like when a `panic!` +call comes from code we call instead of from our code directly: ```rust,should_panic fn main() { From e33f886d6c4dd7d04bf31ee92f4223a09d51a612 Mon Sep 17 00:00:00 2001 From: "Carol (Nichols || Goulding)" Date: Wed, 31 Aug 2016 17:25:41 -0400 Subject: [PATCH 15/53] Technically, panicing isn't the *only* thing we can do here... Explain how this is different from C, which chooses to give you what you want, and how Rust is safer --- ...ch09-01-unrecoverable-errors-with-panic.md | 26 +++++++++++++------ 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/src/ch09-01-unrecoverable-errors-with-panic.md b/src/ch09-01-unrecoverable-errors-with-panic.md index ec393613e9..e534835186 100644 --- a/src/ch09-01-unrecoverable-errors-with-panic.md +++ b/src/ch09-01-unrecoverable-errors-with-panic.md @@ -44,14 +44,24 @@ fn main() { We attempt to access the hundredth element of our vector, but it only has three elements. In this situation, Rust will panic. Using `[]` is supposed to return -an element. But if you pass it an invalid index, there's no number Rust could -return here, it would be wrong. In this case, we've typed in a literal, so in -theory, Rust could figure it out. But if our program was different, say, maybe -reading the index from user input, it would not be possible to determine at -compile time if the index was in bounds. So the only thing that we can do is -terminate the program. - -Let's try to run it: +an element. If you pass `[]` an invalid index, though, there's no element that +Rust could return here that would be correct. In this case, we've typed in a +literal index of `100`, so in theory, Rust could examine our code at compile +time and raise an error at that point. But if our program was different, say, +maybe reading the index from user input, it would not be possible to determine +at compile time if the index was in bounds. + +Other languages like C will attempt to give you exactly what you asked for in +this situation, even though it isn't what you want: you'll get whatever is at +the location in memory that would correspond to that element in the vector, +even though the memory doesn't belong to the vector. This is called a *buffer +overread*, and can lead to security vulnerabilities if an attacker can +manipulate the index in such a way as to read data they shouldn't be allowed to +that is stored after the array. + +In order to protect your program from this sort of vulnerability, if you try to +read an element at an index that doesn't exist, Rust will terminate the program. +Let's try it and see: ```bash $ cargo run From b86cf13fd78f5dc8bdf7afba260a45c2247633db Mon Sep 17 00:00:00 2001 From: "Carol (Nichols || Goulding)" Date: Wed, 31 Aug 2016 17:26:38 -0400 Subject: [PATCH 16/53] Word wrap --- src/ch09-01-unrecoverable-errors-with-panic.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/ch09-01-unrecoverable-errors-with-panic.md b/src/ch09-01-unrecoverable-errors-with-panic.md index e534835186..6c590e7e64 100644 --- a/src/ch09-01-unrecoverable-errors-with-panic.md +++ b/src/ch09-01-unrecoverable-errors-with-panic.md @@ -74,10 +74,10 @@ note: Run with `RUST_BACKTRACE=1` for a backtrace. error: Process didn't exit successfully: `target/debug/panic` (exit code: 101) ``` -This points at a file we didn't write, `../src/libcollections/vec.rs`, line 1265. -That's the implementation of `Vec` in the standard library. While it's easy -to see in this short program where the error was, it would be nicer if we could -have Rust tell us what line in our program caused the error. +This points at a file we didn't write, `../src/libcollections/vec.rs`, line +1265. That's the implementation of `Vec` in the standard library. While it's +easy to see in this short program where the error was, it would be nicer if we +could have Rust tell us what line in our program caused the error. That's what the next line, the `note` is about. If we set the `RUST_BACKTRACE` environment variable, we'll get a backtrace of exactly how the error happend. From f9b690e5144ba2fd28d8c6f6b79bbe93fca1f26e Mon Sep 17 00:00:00 2001 From: "Carol (Nichols || Goulding)" Date: Wed, 31 Aug 2016 20:01:18 -0400 Subject: [PATCH 17/53] Calling panic should be rare; this makes a nice segue --- src/ch09-01-unrecoverable-errors-with-panic.md | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/ch09-01-unrecoverable-errors-with-panic.md b/src/ch09-01-unrecoverable-errors-with-panic.md index 6c590e7e64..7010ce4fee 100644 --- a/src/ch09-01-unrecoverable-errors-with-panic.md +++ b/src/ch09-01-unrecoverable-errors-with-panic.md @@ -118,4 +118,16 @@ error: Process didn't exit successfully: `target/debug/panic` (exit code: 101) ``` That's a lot of output! Line `11` there has the line in our project: -`src/main.rs` line four. +`src/main.rs` line four. The key to reading the backtrace is to start from the +top and read until we see code that we wrote: that's where the problem +originated. If we didn't want our program to panic here, this line is where we +would start investigating in order to figure out how we got to this location +with the values that made our code panic. + +Because `panic!` ends the program with no opportunity to recover, it's best to +not `panic!` in library crates if at all possible. That way, people who use your +crate get to decide how they want to handle failures from your code, instead of +you deciding for them. A common pattern when you're writing a binary crate is +to only `panic!` in the `main` function if an error condition ends up there, +and to use recoverable errors in all the library functions that might fail. +Let's look at how to make errors recoverable. From 6f581c30c2019ddee17d180121357557d5712aba Mon Sep 17 00:00:00 2001 From: "Carol (Nichols || Goulding)" Date: Wed, 31 Aug 2016 17:26:41 -0400 Subject: [PATCH 18/53] Small wording tweaks --- ...ch09-01-unrecoverable-errors-with-panic.md | 2 +- src/ch09-02-recoverable-errors-with-result.md | 38 ++++++++++--------- 2 files changed, 22 insertions(+), 18 deletions(-) diff --git a/src/ch09-01-unrecoverable-errors-with-panic.md b/src/ch09-01-unrecoverable-errors-with-panic.md index 7010ce4fee..bbd5c2d6f2 100644 --- a/src/ch09-01-unrecoverable-errors-with-panic.md +++ b/src/ch09-01-unrecoverable-errors-with-panic.md @@ -81,7 +81,7 @@ could have Rust tell us what line in our program caused the error. That's what the next line, the `note` is about. If we set the `RUST_BACKTRACE` environment variable, we'll get a backtrace of exactly how the error happend. -Let's try it: +Let's try that: ```bash $ RUST_BACKTRACE=1 cargo run diff --git a/src/ch09-02-recoverable-errors-with-result.md b/src/ch09-02-recoverable-errors-with-result.md index 9c60f37115..bf8ec70bed 100644 --- a/src/ch09-02-recoverable-errors-with-result.md +++ b/src/ch09-02-recoverable-errors-with-result.md @@ -16,9 +16,10 @@ enum Result { } ``` -We have `Ok` for successful results, and `Err` for ones that have an error. -These two variants each contain one thing: in `Ok`'s case, it's the successful -return value. With `Err`, it's some type that represents the error. +The `Ok` variant indicates a successful result, and `Err` indicates an +unsuccessful result. These two variants each contain one thing: in `Ok`'s case, +it's the successful return value. With `Err`, it's some type that represents +the error. As an example, let's try opening a file: @@ -101,9 +102,9 @@ your program to blow up, and that's not good. ## Propagating errors with `?` -Sometimes, when writing a function, you don't want to handle the error where -you are, but instead would prefer to return the error to the calling function. -Something like this: +When writing a function, if you don't want to handle the error where you are, +you can return the error to the calling function. Within your function, that +would look like: ```rust use std::fs::File; @@ -137,10 +138,11 @@ let f = File::open("hello.txt")?; ``` The `?` operator at the end of the `open` call does the same thing as our -previous example: It will return the value of an `Ok`, but return the value of -an `Err` to our caller. +previous example: It will return the value inside an `Ok` to the binding `f`, +but will return early out of the whole function and give any `Err` value we get +to our caller. -There's one problem though: let's try compiling the example: +There's one problem though; let's try compiling the example: ```rust,ignore Compiling result v0.1.0 (file:///projects/result) @@ -158,8 +160,8 @@ error: aborting due to previous error ``` What gives? The issue is that the `main()` function has a return type of `()`, -but the question mark operator is trying to return a result. This doesn't work. -Instead of `main()`, let's create a function that returns a result: +but the question mark operator is trying to return a `Result`. This doesn't +work. Instead of `main()`, let's create a function that returns a `Result`: ```rust #![feature(question_mark)] @@ -176,13 +178,15 @@ pub fn process_file() -> Result<(), io::Error> { } ``` -Since the result type has two type parameters, we need to include them. In this -case, `File::open` returns an `std::io::Error`, so we will use it as our error +Since the `Result` type has two type parameters, we need to include them. In +this case, `File::open` returns `std::io::Error`, so we will use it as our error type. But what about success? This function is executed purely for its side -effects; nothing is returned upon success. Well, functions with no return type, -as we just saw with `main()`, are the same as returning unit. So we can use -it as the return type here, too. This leads to the last line of the function, -the slightly silly-looking `Ok(())`. This is an `Ok()` with a `()` inside. +effects; no value is returned when everything works. Functions with no return +type, as we just saw with `main()`, are the same as returning the unit type, +`()`. So we can use the unit type as the return type here, too. + +This leads us to the last line of the function, the slightly silly-looking +`Ok(())`. This is an `Ok()` with a `()` value inside. In chapter XX, we'll learn how to make our own types like these, but for now, an understanding of the core `Result` is enough. From e8cba590c92d99028d6f28907aa14fa02cfeece7 Mon Sep 17 00:00:00 2001 From: "Carol (Nichols || Goulding)" Date: Thu, 1 Sep 2016 10:12:46 -0400 Subject: [PATCH 19/53] Explain the type parameters a little since this is the first time --- src/ch09-02-recoverable-errors-with-result.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/ch09-02-recoverable-errors-with-result.md b/src/ch09-02-recoverable-errors-with-result.md index bf8ec70bed..52ba3d205e 100644 --- a/src/ch09-02-recoverable-errors-with-result.md +++ b/src/ch09-02-recoverable-errors-with-result.md @@ -18,8 +18,12 @@ enum Result { The `Ok` variant indicates a successful result, and `Err` indicates an unsuccessful result. These two variants each contain one thing: in `Ok`'s case, -it's the successful return value. With `Err`, it's some type that represents -the error. +it's the successful return value. With `Err`, it's some value that represents +the error. The `T` and `E` are generic type parameters; we'll go into generics +in more detail in Chapter XX. What you need to know for right now is that the +`Result` type is defined such that it can have the same behavior for any type +`T` that is what we want to return in the success case, and any type `E` that +is what we want to return in the error case. As an example, let's try opening a file: From 0f5a5e67b9ce408d42d15838d5b7326c42e4ad3c Mon Sep 17 00:00:00 2001 From: "Carol (Nichols || Goulding)" Date: Thu, 1 Sep 2016 10:14:24 -0400 Subject: [PATCH 20/53] Leave some notes about ghosting code differences --- src/ch09-02-recoverable-errors-with-result.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/ch09-02-recoverable-errors-with-result.md b/src/ch09-02-recoverable-errors-with-result.md index 52ba3d205e..1f7b256f07 100644 --- a/src/ch09-02-recoverable-errors-with-result.md +++ b/src/ch09-02-recoverable-errors-with-result.md @@ -41,6 +41,8 @@ not yet exist. Before we can do anything with our `File`, we need to extract it out of the result. Let's start with a basic tool: `match`. We've used it to deal with enums previously. + + ```rust,should_panic use std::fs::File; @@ -70,6 +72,8 @@ communicate intent well. The `Result` type has many helper methods defined it to do various things. "Panic on an error result" is one of those methods, and it's called `unwrap`: + + ```rust,should_panic use std::fs::File; @@ -84,6 +88,8 @@ returns `Ok`, return the value inside. If it's an `Err`, panic. There's also another method, similar to unwrap, that lets us choose the error message: `expect`. It looks like this: + + ```rust,should_panic use std::fs::File; @@ -110,6 +116,7 @@ When writing a function, if you don't want to handle the error where you are, you can return the error to the calling function. Within your function, that would look like: + ```rust use std::fs::File; @@ -130,6 +137,8 @@ you're ready to deal with them. This pattern is so common in Rust that there is dedicated syntax for it: the question mark operator. We could have also written the example like this: + + ```rust #![feature(question_mark)] From b2929bf71ca572ad5f80fe625fce9fffc3c43226 Mon Sep 17 00:00:00 2001 From: "Carol (Nichols || Goulding)" Date: Thu, 1 Sep 2016 10:14:39 -0400 Subject: [PATCH 21/53] Use bash syntax highlighting for console output --- src/ch09-02-recoverable-errors-with-result.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ch09-02-recoverable-errors-with-result.md b/src/ch09-02-recoverable-errors-with-result.md index 1f7b256f07..0bc673b410 100644 --- a/src/ch09-02-recoverable-errors-with-result.md +++ b/src/ch09-02-recoverable-errors-with-result.md @@ -62,7 +62,7 @@ we have to decide what to do with it. The simplest thing is to turn our error into a `panic!` instead, by calling the macro. And since we haven't created that file yet, we'll see it in the error message: -```text +```bash thread 'main' panicked at 'There was a problem opening the file: Error { repr: Os { code: 2, message: "No such file or directory" } }', src/main.rs:8 ``` From 79bac99c21bba7b77c4b3e1eec21b2aca1f9c896 Mon Sep 17 00:00:00 2001 From: "Carol (Nichols || Goulding)" Date: Thu, 1 Sep 2016 10:15:49 -0400 Subject: [PATCH 22/53] Use parens when referring to fns inline; clarify some things --- src/ch09-02-recoverable-errors-with-result.md | 32 +++++++++++-------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/src/ch09-02-recoverable-errors-with-result.md b/src/ch09-02-recoverable-errors-with-result.md index 0bc673b410..af221e39d8 100644 --- a/src/ch09-02-recoverable-errors-with-result.md +++ b/src/ch09-02-recoverable-errors-with-result.md @@ -70,7 +70,7 @@ Os { code: 2, message: "No such file or directory" } }', src/main.rs:8 This works okay. However, `match` can be a bit verbose, and it doesn't always communicate intent well. The `Result` type has many helper methods defined it to do various things. "Panic on an error result" is one of those -methods, and it's called `unwrap`: +methods, and it's called `unwrap()`: @@ -82,11 +82,13 @@ fn main() { } ``` -This has the same behavior as our previous example: If the call to `open` +This has the same behavior as our previous example: If the call to `open()` returns `Ok`, return the value inside. If it's an `Err`, panic. -There's also another method, similar to unwrap, that lets us choose the error -message: `expect`. It looks like this: +There's also another method, similar to `unwrap()`, that lets us choose the +error message: `expect()`. Using `expect()` instead of `unwrap()` and providing +good error messages can convey your intent and make tracking down the source of +a panic easier. `expect()` looks like this: @@ -99,16 +101,18 @@ fn main() { ``` This isn't the only way to deal with errors, however. This entire section is -supposed to be about recovering from errors, but we've gone back to panic. -This is true, and gets at an underlying truth: you can easily turn a -recoverable error into an unrecoverable one with `unwrap`, but you can't turn -an unrecoverable error into a recoverable one. This is why good Rust code -chooses to make errors recoverable: you give your caller options. - -The Rust community has a love/hate relationship with `unwrap`. It's useful -in tests, and in examples where you don't want to muddy the example with proper -error handling. But if used in library code, mis-using that library can cause -your program to blow up, and that's not good. +supposed to be about recovering from errors, but we've gone back to panic. This +observation gets at an underlying truth: you can easily turn a recoverable +error into an unrecoverable one with `unwrap()` or `expect()`, but you can't +turn an unrecoverable `panic!` into a recoverable one. This is why good Rust +code chooses to make errors recoverable: you give your caller options. + +The Rust community has a love/hate relationship with `unwrap()` and `expect()`. +They're useful in tests since they will cause the test to fail if there's an +error anyplace you call them. In examples, you might not want to muddy the code +with proper error handling. But if you use them in a library, mis-using your +library can cause other people's programs to halt unexpectedly, and that's not +very user-friendly. ## Propagating errors with `?` From b88aad0d98f78edde78ecaab9d3f2a79df41e653 Mon Sep 17 00:00:00 2001 From: "Carol (Nichols || Goulding)" Date: Thu, 1 Sep 2016 10:51:16 -0400 Subject: [PATCH 23/53] Make these code examples build off each other Yes, this means we can't test these, but imo this makes more sense. --- src/ch09-02-recoverable-errors-with-result.md | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/ch09-02-recoverable-errors-with-result.md b/src/ch09-02-recoverable-errors-with-result.md index af221e39d8..27e7b5ecb6 100644 --- a/src/ch09-02-recoverable-errors-with-result.md +++ b/src/ch09-02-recoverable-errors-with-result.md @@ -121,9 +121,9 @@ you can return the error to the calling function. Within your function, that would look like: -```rust -use std::fs::File; +```rust,ignore +# use std::fs::File; # fn foo() -> std::io::Result<()> { let f = File::open("hello.txt"); @@ -143,15 +143,14 @@ the example like this: -```rust +```rust,ignore #![feature(question_mark)] use std::fs::File; -# fn foo() -> std::io::Result<()> { -let f = File::open("hello.txt")?; -# Ok(()) -# } +fn main() { + let f = File::open("hello.txt")?; +} ``` The `?` operator at the end of the `open` call does the same thing as our From 7095fd14ab4fc9360d4f716293a02bd913e12b76 Mon Sep 17 00:00:00 2001 From: "Carol (Nichols || Goulding)" Date: Thu, 1 Sep 2016 10:51:56 -0400 Subject: [PATCH 24/53] TODO; added some ideas for continuing recoverable section This ends pretty abruptly and has left some things out that I think would be useful here. --- src/ch09-02-recoverable-errors-with-result.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/ch09-02-recoverable-errors-with-result.md b/src/ch09-02-recoverable-errors-with-result.md index 27e7b5ecb6..e789677963 100644 --- a/src/ch09-02-recoverable-errors-with-result.md +++ b/src/ch09-02-recoverable-errors-with-result.md @@ -204,5 +204,7 @@ type, as we just saw with `main()`, are the same as returning the unit type, This leads us to the last line of the function, the slightly silly-looking `Ok(())`. This is an `Ok()` with a `()` value inside. -In chapter XX, we'll learn how to make our own types like these, but for now, -an understanding of the core `Result` is enough. +TODO: +- an example returning something other than () +- mention you'd still do a `match` in main where you call this function +- show how to chain From cd7932f0dd2c7566beae665342856042c289f5eb Mon Sep 17 00:00:00 2001 From: "Carol (Nichols || Goulding)" Date: Thu, 1 Sep 2016 10:53:30 -0400 Subject: [PATCH 25/53] tools -> features --- src/ch09-00-error-handling.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ch09-00-error-handling.md b/src/ch09-00-error-handling.md index 4b00740dd1..10f714ee2c 100644 --- a/src/ch09-00-error-handling.md +++ b/src/ch09-00-error-handling.md @@ -1,7 +1,7 @@ # Error Handling Rust's laser-focus on safety spills over into a related area: error handling. -Errors are a fact of life in software, so Rust has a number of tools that you +Errors are a fact of life in software, so Rust has a number of features that you can use to handle situations in which something bad happens. In many cases, Rust requires you to acknowledge the possibility of an error occurring and take some action in that situation. This makes your program safer and more robust by From 9154816eb5339faf205c4bdde51c09633af35340 Mon Sep 17 00:00:00 2001 From: "Carol (Nichols || Goulding)" Date: Thu, 1 Sep 2016 10:54:15 -0400 Subject: [PATCH 26/53] splits -> groups --- src/ch09-00-error-handling.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ch09-00-error-handling.md b/src/ch09-00-error-handling.md index 10f714ee2c..8f1ab3dfb4 100644 --- a/src/ch09-00-error-handling.md +++ b/src/ch09-00-error-handling.md @@ -8,7 +8,7 @@ some action in that situation. This makes your program safer and more robust by eliminating the possibility of unexpected errors being discovered late in the development process. -Rust splits errors into two major kinds: errors that are recoverable, and +Rust groups errors into two major kinds: errors that are recoverable, and errors that are not recoverable. What does it mean to "recover" from an error? In the simplest sense, it relates to the answer of this question: From 2d40c73e998af8015a28e6648bc74fd7877f059b Mon Sep 17 00:00:00 2001 From: "Carol (Nichols || Goulding)" Date: Wed, 14 Sep 2016 18:41:38 -0400 Subject: [PATCH 27/53] More edits after more readings forever --- ...ch09-01-unrecoverable-errors-with-panic.md | 20 ++++---- src/ch09-02-recoverable-errors-with-result.md | 46 +++++++++++-------- 2 files changed, 36 insertions(+), 30 deletions(-) diff --git a/src/ch09-01-unrecoverable-errors-with-panic.md b/src/ch09-01-unrecoverable-errors-with-panic.md index bbd5c2d6f2..dc45531c3c 100644 --- a/src/ch09-01-unrecoverable-errors-with-panic.md +++ b/src/ch09-01-unrecoverable-errors-with-panic.md @@ -60,8 +60,8 @@ manipulate the index in such a way as to read data they shouldn't be allowed to that is stored after the array. In order to protect your program from this sort of vulnerability, if you try to -read an element at an index that doesn't exist, Rust will terminate the program. -Let's try it and see: +read an element at an index that doesn't exist, Rust will stop execution and +refuse to continue with an invalid value. Let's try it and see: ```bash $ cargo run @@ -119,15 +119,11 @@ error: Process didn't exit successfully: `target/debug/panic` (exit code: 101) That's a lot of output! Line `11` there has the line in our project: `src/main.rs` line four. The key to reading the backtrace is to start from the -top and read until we see code that we wrote: that's where the problem +top and read until we see files that we wrote: that's where the problem originated. If we didn't want our program to panic here, this line is where we would start investigating in order to figure out how we got to this location -with the values that made our code panic. - -Because `panic!` ends the program with no opportunity to recover, it's best to -not `panic!` in library crates if at all possible. That way, people who use your -crate get to decide how they want to handle failures from your code, instead of -you deciding for them. A common pattern when you're writing a binary crate is -to only `panic!` in the `main` function if an error condition ends up there, -and to use recoverable errors in all the library functions that might fail. -Let's look at how to make errors recoverable. +with values that cause the panic. + +Now that we've covered how to `panic!` to stop our code's execution and how to +debug a `panic!`, let's look at how to instead return and use recoverable +errors with `Result`. diff --git a/src/ch09-02-recoverable-errors-with-result.md b/src/ch09-02-recoverable-errors-with-result.md index e789677963..9e7c5dd8c6 100644 --- a/src/ch09-02-recoverable-errors-with-result.md +++ b/src/ch09-02-recoverable-errors-with-result.md @@ -57,10 +57,11 @@ error), } ``` -If we see an `Ok`, we can return the inner `file` out of it. If we see `Err`, -we have to decide what to do with it. The simplest thing is to turn our error -into a `panic!` instead, by calling the macro. And since we haven't created -that file yet, we'll see it in the error message: +If we see an `Ok`, we can return the inner `file` out of the `Ok` variant. If +we see `Err`, we have to decide what to do with it. The simplest thing is to +turn our error into a `panic!` instead, by calling the macro. And since we +haven't created that file yet, we'll see a message indicating as such when we +print the error value: ```bash thread 'main' panicked at 'There was a problem opening the file: Error { repr: @@ -69,7 +70,7 @@ Os { code: 2, message: "No such file or directory" } }', src/main.rs:8 This works okay. However, `match` can be a bit verbose, and it doesn't always communicate intent well. The `Result` type has many helper methods -defined it to do various things. "Panic on an error result" is one of those +defined on it to do various things. "Panic on an error result" is one of those methods, and it's called `unwrap()`: @@ -85,7 +86,7 @@ fn main() { This has the same behavior as our previous example: If the call to `open()` returns `Ok`, return the value inside. If it's an `Err`, panic. -There's also another method, similar to `unwrap()`, that lets us choose the +There's also another method, similar to `unwrap()`, but that lets us choose the error message: `expect()`. Using `expect()` instead of `unwrap()` and providing good error messages can convey your intent and make tracking down the source of a panic easier. `expect()` looks like this: @@ -105,7 +106,7 @@ supposed to be about recovering from errors, but we've gone back to panic. This observation gets at an underlying truth: you can easily turn a recoverable error into an unrecoverable one with `unwrap()` or `expect()`, but you can't turn an unrecoverable `panic!` into a recoverable one. This is why good Rust -code chooses to make errors recoverable: you give your caller options. +code chooses to make errors recoverable: you give your caller choices. The Rust community has a love/hate relationship with `unwrap()` and `expect()`. They're useful in tests since they will cause the test to fail if there's an @@ -177,7 +178,8 @@ error: aborting due to previous error What gives? The issue is that the `main()` function has a return type of `()`, but the question mark operator is trying to return a `Result`. This doesn't -work. Instead of `main()`, let's create a function that returns a `Result`: +work. Instead of `main()`, let's create a function that returns a `Result` so +that we are allowed to use the question mark operator: ```rust #![feature(question_mark)] @@ -194,17 +196,25 @@ pub fn process_file() -> Result<(), io::Error> { } ``` -Since the `Result` type has two type parameters, we need to include them. In -this case, `File::open` returns `std::io::Error`, so we will use it as our error -type. But what about success? This function is executed purely for its side -effects; no value is returned when everything works. Functions with no return -type, as we just saw with `main()`, are the same as returning the unit type, -`()`. So we can use the unit type as the return type here, too. +Since the `Result` type has two type parameters, we need to include them both +in our function signature. In this case, `File::open` returns `std::io::Error`, +so we will use it as our error type. But what about success? This function is +executed purely for its side effects; no value is returned when everything +works. Functions with no return type, as we just saw with `main()`, are the +same as returning the unit type, `()`. So we can use the unit type as the +return type here, too. This leads us to the last line of the function, the slightly silly-looking `Ok(())`. This is an `Ok()` with a `()` value inside. -TODO: -- an example returning something other than () -- mention you'd still do a `match` in main where you call this function -- show how to chain +Now we have the function `process_file` that uses the question mark operator and compiles successfully. We can call this function and handle the return value in any other function that returns a `Result` by also using the question mark operator there, and that would look like `process_file()?`. However, the `main` function still returns `()`, so we would still have to use a `match` statement, an `unwrap()`, or an `expect()` if we wanted to call `process_file` from `main`. Using `expect()` would look like this: + +```rust,ignore +fn main() { + process_file().expect("There was a problem processing the file"); +} +``` + +## Chaining Question Mark Operators + +TODO From 48c6890c08471b80300774e6115f4dcd3a615fe8 Mon Sep 17 00:00:00 2001 From: "Carol (Nichols || Goulding)" Date: Wed, 14 Sep 2016 14:04:17 -0400 Subject: [PATCH 28/53] Add a section on deciding how to handle errors --- src/SUMMARY.md | 1 + src/ch09-00-error-handling.md | 4 +- src/ch09-03-to-panic-or-not-to-panic.md | 142 ++++++++++++++++++++++++ 3 files changed, 146 insertions(+), 1 deletion(-) create mode 100644 src/ch09-03-to-panic-or-not-to-panic.md diff --git a/src/SUMMARY.md b/src/SUMMARY.md index 2cd19129e8..5cbadd700a 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -39,6 +39,7 @@ - [Error Handling](ch09-00-error-handling.md) - [Unrecoverable errors with panic!](ch09-01-unrecoverable-errors-with-panic.md) - [Recoverable errors with `Result`](ch09-02-recoverable-errors-with-result.md) + - [To `panic!` or Not To `panic!`](ch09-03-to-panic-or-not-to-panic.md) - [Lifetimes]() diff --git a/src/ch09-00-error-handling.md b/src/ch09-00-error-handling.md index 8f1ab3dfb4..d6790005b9 100644 --- a/src/ch09-00-error-handling.md +++ b/src/ch09-00-error-handling.md @@ -18,4 +18,6 @@ error? In the simplest sense, it relates to the answer of this question: The technique that you use depends on the answer to this question. First, we'll talk about `panic!`, Rust's way of signaling an unrecoverable error. Then, we'll talk about `Result`, the return type for functions that -may return an error you can recover from. +may return an error you can recover from. Finally, we'll discuss considerations +to take into account when deciding whether to try to recover from an error or +to stop execution. diff --git a/src/ch09-03-to-panic-or-not-to-panic.md b/src/ch09-03-to-panic-or-not-to-panic.md new file mode 100644 index 0000000000..328c30331b --- /dev/null +++ b/src/ch09-03-to-panic-or-not-to-panic.md @@ -0,0 +1,142 @@ +## To `panic!` or Not To `panic!` + +So how do you decide when you should call `panic!` and when you should return +`Result`? The most concise answer is to `panic!` in `main`, at your outermost +layer, and return `Result` everywhere else in your code. Especially when you're +writing a library for others to use, it's best to not `panic!` if at all +possible. That way, people who use your crate get to decide how they want to +handle failures from your code, instead of you deciding for them. + +But that answer is simplistic. There are cases where you might want to call +`panic!` in library code that have to do with Rust's quest for safety. Let's +look at some more nuanced guidelines. + +### Guidelines for Error Handling + +`panic!` when your code is in a situation where it's possible to be in a bad +state and: + +* The cause of the bad state is not your code, it's caused by code that's + calling your code or code that your code is calling that's out of your control +* The bad state is not something that's *expected* to happen occasionally +* Your code after this point needs to rely on not being in this bad state +* There's not a good way to encode this information in the types you use + +Taking these in turn: + +A bad state consists of things like invalid values, contradictory values, or +nothing when you expect to have something. If someone calls your code and +passes in values that don't make sense, the best thing might be to `panic!` and +alert the person using your library to the bug in their code so that they can +fix it during development. Similarly, `panic!` is often appropriate if you call +someone else's code that is out of your control, and it returns an invalid +state that you have no way of fixing. Getting null pointers back from calling +functions in C is an example of this situation. If the only place the bug could +possibly come from is your own code, however, you should fix your bug! + +Some bad states are expected to happen sometimes, and will happen no matter how +well you write your code. Example of this include a parser being given +malformed data to parse, or an HTTP request returning a status that indicates +you have hit a rate limit. In these cases, you should indicate that failure is +an expected possibility by returning a `Result` and propagate these bad states +upwards so that the caller can decide how they would like to handle the +problem. `panic!` would not be the best way to handle these cases. + +When your code performs operations on values, your code should verify the +values are valid first, then proceed confidently with the operations. This is +mostly for safety reasons: attempting to operate on invalid data can expose +your code to vulnerabilities. This is the main reason that the standard library +will `panic!` if you attempt an out-of-bounds array access: trying to access +memory that doesn't belong to the current data structure is a common security +problem. A less important reason is that it makes your code more organized to +have the error checking first and the rest of the logic afterwards, rather than +interspersing error checks in with your logic. + +Having lots of error checks in all of your functions would be verbose and +annoying, though. Luckily, our last guideline has a tip for this situation: use +Rust's type system (and thus the type checking the compiler does) to do a lot +of the checks for you. If your function takes a particular type as an argument, +you can proceed with your code's logic knowing that the compiler has already +ensured you have a valid value. For example, if you have a type rather than an +`Option`, you know that you will have something rather than nothing and you +don't have to have an explicit check to make sure. Another example is using an +unsigned integer type like `u32`, which ensures the argument value is never +negative. + +### Creating Custom Types for Validation + +Going a step further with the idea of using Rust's type system to ensure we +have a valid value, let's look at an example of creating a custom type in this +situation. Recall the guessing game in Chapter 2, where our code asked the user +to guess a number between 1 and 100. We actually never validated that the +user's guess was between those numbers before checking it against our secret +number, only that it was positive. In this case, the consequences were not very +dire: our output of "Too high" or "Too low" would still be correct. It would be +a nice enhancement to guide the user towards valid guesses, though. We could +add a check after we parse the guess: + +```rust,ignore +loop { + // snip + + let guess: u32 = 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; + } + + match guess.cmp(&secret_number) { + // snip +} +``` + + + +The `if` expression checks to see if our value is out of range, tells the user +about the problem, and calls `continue` to start the next iteration of the loop +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. + +If we had a situation where it was absolutely critical we had a value between 1 +and 100, and we had many functions that had this requirement, it would be +tedious (and potentially impact performance) to have a check like this in every +function. Instead, we can make a new type and put the validations in one place, +in the type's constructor. Then our functions can use the type with the +confidence that we have values that meet our requirements. Here's an example of +one way to define a `Guess` type that will only create an instance of `Guess` +if the `new` function gets a value between 1 and 100: + +```rust +struct Guess { + value: u32, +} + +impl Guess { + fn new(value: u32) -> Guess { + if value < 1 || value > 100 { + panic!("Guess value must be between 1 and 100, got {}.", value); + } + Guess { + value: value, + } + } +} +``` + +A function that takes as an argument or returns only numbers between 1 and 100 +could then declare in its signature to take a `Guess` rather than a `u32`, and +would not need to do any additional checks in its body. + +One last guideline: since users of our library will expect `panic!` to be rare, +if we decide a library function fits these guidelines and should call `panic!`, +it's a good idea to document which functions `panic!` and in what conditions. +That way, users of our library can make sure their code uses our library +properly, or at least there is an explanation for any `panic!` they get from +our code. + +Now that we've reduced duplication in our validation code, let's look at a feature of Rust that helps reduce duplication in lots of code: generics! From c1c40d532c2839ea1e61744cba9d48e0c2c62ca2 Mon Sep 17 00:00:00 2001 From: "Carol (Nichols || Goulding)" Date: Tue, 11 Oct 2016 15:41:37 -0400 Subject: [PATCH 29/53] Rework intro error handling section --- src/ch09-00-error-handling.md | 41 +++++++++++++++++++---------------- 1 file changed, 22 insertions(+), 19 deletions(-) diff --git a/src/ch09-00-error-handling.md b/src/ch09-00-error-handling.md index d6790005b9..9f4a1df647 100644 --- a/src/ch09-00-error-handling.md +++ b/src/ch09-00-error-handling.md @@ -1,23 +1,26 @@ # Error Handling -Rust's laser-focus on safety spills over into a related area: error handling. -Errors are a fact of life in software, so Rust has a number of features that you -can use to handle situations in which something bad happens. In many cases, -Rust requires you to acknowledge the possibility of an error occurring and take -some action in that situation. This makes your program safer and more robust by -eliminating the possibility of unexpected errors being discovered late in the -development process. +Rust's focus on reliability extends to the area of error handling. Errors are a +fact of life in software, so Rust has a number of features that you can use to +handle situations in which something bad happens. In many cases, Rust requires +you to acknowledge the possibility of an error occurring and take some action +in that situation. This makes your program more robust by eliminating the +possibility of unexpected errors only being discovered after you've deployed +your code to production. -Rust groups errors into two major kinds: errors that are recoverable, and -errors that are not recoverable. What does it mean to "recover" from an -error? In the simplest sense, it relates to the answer of this question: +Rust groups errors into two major kinds: errors that are *recoverable*, and +errors that are *unrecoverable*. Recoverable errors are problems like a file not +being found, where it's usually reasonable to report that problem to the user +and retry the operation. Unrecoverable errors are problems like trying to +access a location beyond the end of an array, and these are always symptoms of +bugs. -> If I call a function, and something bad happens, can I do something -> meaningful? Or should execution stop? - -The technique that you use depends on the answer to this question. First, -we'll talk about `panic!`, Rust's way of signaling an unrecoverable error. -Then, we'll talk about `Result`, the return type for functions that -may return an error you can recover from. Finally, we'll discuss considerations -to take into account when deciding whether to try to recover from an error or -to stop execution. +Most languages do not distinguish between the two kinds of errors, so they +handle both kinds in the same way using mechanisms like exceptions. Rust +doesn't have exceptions. Instead, it has the value `Result` to return in +the case of recoverable errors and the `panic!` macro that stops execution when +it encounters unrecoverable errors. This chapter will cover the more +straightforward case of calling `panic!` first. Then, we'll talk about +returning `Result` values and calling functions that return `Result`. Finally, we'll discuss considerations to take into account when deciding +whether to try to recover from an error or to stop execution. From e10d974b233356236f4785ca70bf0c78e8841fd5 Mon Sep 17 00:00:00 2001 From: "Carol (Nichols || Goulding)" Date: Tue, 11 Oct 2016 15:42:53 -0400 Subject: [PATCH 30/53] Update reason why we can't test beta rn --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 1e0a1d66cb..afe0575191 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,7 +4,7 @@ cache: cargo rust: - nightly # - beta -# ^ to be changed once a new beta is cut that doesn't crash +# ^ to be changed once question_mark is in beta # - stable # ^ to be changed once question_mark becomes stable before_script: From da9422bc96df89ded18b4fd0db92daf32673cd73 Mon Sep 17 00:00:00 2001 From: "Carol (Nichols || Goulding)" Date: Tue, 11 Oct 2016 17:28:48 -0400 Subject: [PATCH 31/53] Add a sidebar on unwind vs abort --- ...ch09-01-unrecoverable-errors-with-panic.md | 25 +++++++++++++++---- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/src/ch09-01-unrecoverable-errors-with-panic.md b/src/ch09-01-unrecoverable-errors-with-panic.md index dc45531c3c..8652b05a26 100644 --- a/src/ch09-01-unrecoverable-errors-with-panic.md +++ b/src/ch09-01-unrecoverable-errors-with-panic.md @@ -2,11 +2,26 @@ Sometimes, bad things happen, and there's nothing that you can do about it. For these cases, Rust has a macro, `panic!`. When this macro executes, your program -will print a failure message and then quit. The most common reason for this is -when a bug of some kind has been detected, and it's not clear how to handle the -error. - -Let's try it out with a simple program: +will print a failure message, unwind and clean up the stack, and then quit. The +most common reason for this is when a bug of some kind has been detected, and +it's not clear how to handle the error. + + + +> #### Unwinding +> By default, when a `panic!` happens in Rust, the program starts +> *unwinding*, which means Rust walks back up the stack and cleans up the data +> from each function it encounters. Doing that walking and cleanup is a lot of +> work. The alternative is to immediately `abort`, which ends the program +> without cleaning up. Memory that the program was using will need to be cleaned +> up by the operating system. If you're in a situation where you need to make +> the resulting binary as small as possible, you can switch from unwinding on +> panic to aborting on panic by adding `panic = 'abort'` to the appropriate +> `[profile]` sections in your `Cargo.toml` file. + + + +Let's try out calling `panic!()` with a simple program: ```rust,should_panic fn main() { From 6d2a2c4dd70635850ce9de82345aaf8d4376f244 Mon Sep 17 00:00:00 2001 From: "Carol (Nichols || Goulding)" Date: Tue, 11 Oct 2016 17:31:41 -0400 Subject: [PATCH 32/53] Remove distraction about literal 100 --- src/ch09-01-unrecoverable-errors-with-panic.md | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/ch09-01-unrecoverable-errors-with-panic.md b/src/ch09-01-unrecoverable-errors-with-panic.md index 8652b05a26..e8514ac76e 100644 --- a/src/ch09-01-unrecoverable-errors-with-panic.md +++ b/src/ch09-01-unrecoverable-errors-with-panic.md @@ -60,11 +60,7 @@ fn main() { We attempt to access the hundredth element of our vector, but it only has three elements. In this situation, Rust will panic. Using `[]` is supposed to return an element. If you pass `[]` an invalid index, though, there's no element that -Rust could return here that would be correct. In this case, we've typed in a -literal index of `100`, so in theory, Rust could examine our code at compile -time and raise an error at that point. But if our program was different, say, -maybe reading the index from user input, it would not be possible to determine -at compile time if the index was in bounds. +Rust could return here that would be correct. Other languages like C will attempt to give you exactly what you asked for in this situation, even though it isn't what you want: you'll get whatever is at From e98eab8cb58275bd779a6752eedb67638156de46 Mon Sep 17 00:00:00 2001 From: "Carol (Nichols || Goulding)" Date: Tue, 11 Oct 2016 20:59:46 -0400 Subject: [PATCH 33/53] Reworking Result examples til I hit the horrible Carrier error --- src/ch09-02-recoverable-errors-with-result.md | 135 ++++++++++++++---- 1 file changed, 106 insertions(+), 29 deletions(-) diff --git a/src/ch09-02-recoverable-errors-with-result.md b/src/ch09-02-recoverable-errors-with-result.md index 9e7c5dd8c6..e9c85a8b11 100644 --- a/src/ch09-02-recoverable-errors-with-result.md +++ b/src/ch09-02-recoverable-errors-with-result.md @@ -83,7 +83,7 @@ fn main() { } ``` -This has the same behavior as our previous example: If the call to `open()` +This has similar behavior as our previous example: If the call to `open()` returns `Ok`, return the value inside. If it's an `Err`, panic. There's also another method, similar to `unwrap()`, but that lets us choose the @@ -97,7 +97,7 @@ a panic easier. `expect()` looks like this: use std::fs::File; fn main() { - let f = File::open("hello.txt").expect("failed to open hello.txt"); + let f = File::open("hello.txt").expect("Failed to open hello.txt."); } ``` @@ -109,55 +109,132 @@ turn an unrecoverable `panic!` into a recoverable one. This is why good Rust code chooses to make errors recoverable: you give your caller choices. The Rust community has a love/hate relationship with `unwrap()` and `expect()`. -They're useful in tests since they will cause the test to fail if there's an -error anyplace you call them. In examples, you might not want to muddy the code -with proper error handling. But if you use them in a library, mis-using your -library can cause other people's programs to halt unexpectedly, and that's not -very user-friendly. +They're very handy when prototyping, before you're ready to decide how to +handle errors, and in that case they leave clear markers to look for when you +are ready to make your program more robust. They're useful in tests since they +will cause the test to fail if there's an error anyplace you call them. In +examples, you might not want to muddy the code with proper error handling. But +if you use them in a library, mis-using your library can cause other people's +programs to halt unexpectedly, and that's not very user-friendly. ## Propagating errors with `?` When writing a function, if you don't want to handle the error where you are, -you can return the error to the calling function. Within your function, that -would look like: +you can return the error to the calling function. For example, here's 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 this +function: - - -```rust,ignore +```rust # use std::fs::File; -# fn foo() -> std::io::Result<()> { -let f = File::open("hello.txt"); +# use std::io; +# use std::io::Read; +# +fn read_username_from_file() -> Result { + let f = File::open("hello.txt"); + + let mut f = match f { + Ok(file) => file, + Err(e) => return Err(e), + }; -let f = match f { - Ok(file) => file, - Err(e) => return Err(e), -}; + let mut s = String::new(); -# Ok(()) -# } + match f.read_to_string(&mut s) { + Ok(_) => Ok(s), + Err(e) => Err(e), + } +} ``` This is a very common way of handling errors: propagate them upward until you're ready to deal with them. This pattern is so common in Rust that there is -dedicated syntax for it: the question mark operator. We could have also written -the example like this: +a macro for it, `try!`, and as of Rust 1.XX, dedicated syntax for it: the +question mark operator. We could have written the above like this using the +`try!` macro and it would have the same functionality as the `match` expressions: - + -```rust,ignore +```rust +# use std::fs::File; +# use std::io; +# use std::io::Read; +# +fn read_username_from_file() -> Result { + let mut f = try!(File::open("hello.txt")); + let mut s = String::new(); + try!(f.read_to_string(&mut s)); + Ok(s) +} +``` + +Or like this using the question mark operator: + + + +```rust +#![feature(question_mark)] +# fn main() {} +# use std::fs::File; +# use std::io; +# use std::io::Read; +# +fn read_username_from_file() -> Result { + let mut f = File::open("hello.txt")?; + let mut s = String::new(); + f.read_to_string(&mut s)?; + Ok(s) +} +``` + +The `?` operator at the end of the `open` call does the same thing as the +example that uses `match` and the example that uses the `try!` macro: It will +return the value inside an `Ok` to the binding `f`, but will return early out +of the whole function and give any `Err` value we get to our caller. The same +thing applies to the `?` at the end of the `read_to_string` call. + +The advantage of using the question mark operator over the `try!` macro is the +question mark operator permits chaining. We could further shorten this code +by instead doing: + +```rust #![feature(question_mark)] +# fn main() {} +# use std::fs::File; +# use std::io; +# use std::io::Read; +# +fn read_username_from_file() -> Result { + let mut s = String::new(); + File::open("hello.txt")?.read_to_string(&mut s)?; + Ok(s) +} +``` -use std::fs::File; +Much nicer, right? The `try!` macro and the `?` operator make propagating +errors upwards much more ergonomic. There's one catch though: they can only be +used in functions that return a `Result`, since they expand to the same `match` +expression we saw above that had a potential early return of an `Err` value. Let's look at what happens if we try to use `?` in the `main` function, which has a return type of `()`: +```rust,ignore +#![feature(question_mark)] +# use std::fs::File; fn main() { let f = File::open("hello.txt")?; } ``` -The `?` operator at the end of the `open` call does the same thing as our -previous example: It will return the value inside an `Ok` to the binding `f`, -but will return early out of the whole function and give any `Err` value we get -to our caller. +When we compile this, we get the following error message: + +```bash +``` + + + There's one problem though; let's try compiling the example: From 48bed57e671fcb937b30efc8be28f1f3696b8b1a Mon Sep 17 00:00:00 2001 From: "Carol (Nichols || Goulding)" Date: Wed, 12 Oct 2016 10:37:22 -0400 Subject: [PATCH 34/53] Use `try!` for the illustration of calling it in `main` Until rust-lang/rust#35946 is fixed, so that the error message is clearer than not implementing the Carrier trait. --- src/ch09-02-recoverable-errors-with-result.md | 28 ++++++++----------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/src/ch09-02-recoverable-errors-with-result.md b/src/ch09-02-recoverable-errors-with-result.md index e9c85a8b11..c027947fd1 100644 --- a/src/ch09-02-recoverable-errors-with-result.md +++ b/src/ch09-02-recoverable-errors-with-result.md @@ -218,34 +218,30 @@ fn read_username_from_file() -> Result { Much nicer, right? The `try!` macro and the `?` operator make propagating errors upwards much more ergonomic. There's one catch though: they can only be used in functions that return a `Result`, since they expand to the same `match` -expression we saw above that had a potential early return of an `Err` value. Let's look at what happens if we try to use `?` in the `main` function, which has a return type of `()`: +expression we saw above that had a potential early return of an `Err` value. +Let's look at what happens if we try to use `try!` in the `main` function, +which you'll recall has a return type of `()`: ```rust,ignore -#![feature(question_mark)] # use std::fs::File; fn main() { - let f = File::open("hello.txt")?; + let f = try!(File::open("hello.txt")); } ``` + + When we compile this, we get the following error message: ```bash -``` - - - - -There's one problem though; let's try compiling the example: - -```rust,ignore - Compiling result v0.1.0 (file:///projects/result) error[E0308]: mismatched types - --> src/main.rs:6:13 + --> | -6 | let f = File::open("hello.txt")?; - | ^^^^^^^^^^^^^^^^^^^^^^^^ expected (), found enum -`std::result::Result` +3 | let f = try!(File::open("hello.txt")); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected (), found enum `std::result::Result` | = note: expected type `()` = note: found type `std::result::Result<_, _>` From 1eaa218746bd59f39c09273224198074003c4500 Mon Sep 17 00:00:00 2001 From: "Carol (Nichols || Goulding)" Date: Wed, 12 Oct 2016 10:38:32 -0400 Subject: [PATCH 35/53] Finish rearranging the Result section --- src/ch09-02-recoverable-errors-with-result.md | 58 +++++-------------- 1 file changed, 14 insertions(+), 44 deletions(-) diff --git a/src/ch09-02-recoverable-errors-with-result.md b/src/ch09-02-recoverable-errors-with-result.md index c027947fd1..e0aee40091 100644 --- a/src/ch09-02-recoverable-errors-with-result.md +++ b/src/ch09-02-recoverable-errors-with-result.md @@ -117,7 +117,7 @@ examples, you might not want to muddy the code with proper error handling. But if you use them in a library, mis-using your library can cause other people's programs to halt unexpectedly, and that's not very user-friendly. -## Propagating errors with `?` +## Propagating errors with `try!` or `?` When writing a function, if you don't want to handle the error where you are, you can return the error to the calling function. For example, here's a @@ -147,6 +147,12 @@ fn read_username_from_file() -> Result { } ``` +Since the `Result` type has two type parameters, we need to include them both +in our function signature. In this case, `File::open` and `read_to_string` +return `std::io::Error` as the value inside the `Err` variant, so we will also +use it as our error type. If this function succeeds, we want to return the +username as a `String` inside the `Ok` variant, so that is our success type. + This is a very common way of handling errors: propagate them upward until you're ready to deal with them. This pattern is so common in Rust that there is a macro for it, `try!`, and as of Rust 1.XX, dedicated syntax for it: the @@ -245,49 +251,13 @@ error[E0308]: mismatched types | = note: expected type `()` = note: found type `std::result::Result<_, _>` - -error: aborting due to previous error -``` - -What gives? The issue is that the `main()` function has a return type of `()`, -but the question mark operator is trying to return a `Result`. This doesn't -work. Instead of `main()`, let's create a function that returns a `Result` so -that we are allowed to use the question mark operator: - -```rust -#![feature(question_mark)] - -use std::fs::File; -use std::io; - -pub fn process_file() -> Result<(), io::Error> { - let f = File::open("hello.txt")?; - - // do some stuff with f - - Ok(()) -} -``` - -Since the `Result` type has two type parameters, we need to include them both -in our function signature. In this case, `File::open` returns `std::io::Error`, -so we will use it as our error type. But what about success? This function is -executed purely for its side effects; no value is returned when everything -works. Functions with no return type, as we just saw with `main()`, are the -same as returning the unit type, `()`. So we can use the unit type as the -return type here, too. - -This leads us to the last line of the function, the slightly silly-looking -`Ok(())`. This is an `Ok()` with a `()` value inside. - -Now we have the function `process_file` that uses the question mark operator and compiles successfully. We can call this function and handle the return value in any other function that returns a `Result` by also using the question mark operator there, and that would look like `process_file()?`. However, the `main` function still returns `()`, so we would still have to use a `match` statement, an `unwrap()`, or an `expect()` if we wanted to call `process_file` from `main`. Using `expect()` would look like this: - -```rust,ignore -fn main() { - process_file().expect("There was a problem processing the file"); -} ``` -## Chaining Question Mark Operators +The mismatched types that this error is pointing out says the `main()` function +has a return type of `()`, but the `try!` macro is trying to return a `Result`. +So in functions that don't return `Result`, when you call other functions that +return `Result`, you'll need to use a `match` or one of the methods on `Result` +to handle it instead of using `try!` or `?`. -TODO +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 in which cases. From a3dfedb92102345a57c6e94dc8699a1ab985110f Mon Sep 17 00:00:00 2001 From: "Carol (Nichols || Goulding)" Date: Wed, 12 Oct 2016 11:08:45 -0400 Subject: [PATCH 36/53] Add a discussion of matching on error kind --- src/ch09-02-recoverable-errors-with-result.md | 52 ++++++++++++++++++- 1 file changed, 51 insertions(+), 1 deletion(-) diff --git a/src/ch09-02-recoverable-errors-with-result.md b/src/ch09-02-recoverable-errors-with-result.md index e0aee40091..890b82d1fa 100644 --- a/src/ch09-02-recoverable-errors-with-result.md +++ b/src/ch09-02-recoverable-errors-with-result.md @@ -68,7 +68,57 @@ thread 'main' panicked at 'There was a problem opening the file: Error { repr: Os { code: 2, message: "No such file or directory" } }', src/main.rs:8 ``` -This works okay. However, `match` can be a bit verbose, and it doesn't always +## Matching on Different Errors + +There are many reasons why opening a file might fail, and we may not want to +take the same actions to try to recover for all of them. For example, if the +file we're trying to open does not exist, we could choose to create it. If the +file exists but we don't have permission to read it, or any other error, we +still want to `panic!` in the same way as above and not create the file. + +The `Err` type `File::open` returns is [`io::Error`][ioerror], +which is a struct provided by the standard library. This struct has a method +`kind` that we can call to get an [`io::ErrorKind`][iokind] +value that we can use to handle different causes of an `Err` returned from +`File::open` differently: + +[ioerror]: ../std/io/struct.Error.html +[iokind]: ../std/io/enum.ErrorKind.html + +```rust,should_panic +use std::fs::File; +use std::io::ErrorKind; + +fn main() { + let f = File::open("hello.txt"); + + let f = match f { + Ok(file) => file, + Err(ref error) if error.kind() == ErrorKind::NotFound => { + match File::create("hello.txt") { + Ok(fc) => fc, + Err(e) => panic!("Tried to create file but there was a problem: {:?}", e), + } + }, + Err(error) => panic!("There was a problem opening the file: {:?}", +error), + }; +} +``` + + + +This example uses a *match guard* with the second arm's pattern to add a +condition that further refines the pattern. The `ref` in the pattern is needed +so that the `error` is not moved into the guard condition. The condition we +want to check is that the value `error.kind()` returns is the `NotFound` +variant of the `ErrorKind` enum. Note that `File::create` could also fail, so +we need to add an inner `match` statement as well! The last arm of the outer +`match` stays the same to panic on any error besides the file not being found. + +## Shortcuts for Panic on Error: `unwrap` and `expect` + +Using `match` works okay but can be a bit verbose, and it doesn't always communicate intent well. The `Result` type has many helper methods defined on it to do various things. "Panic on an error result" is one of those methods, and it's called `unwrap()`: From 34b346c615d8852ecd47fc4af6681b5e07f72271 Mon Sep 17 00:00:00 2001 From: "Carol (Nichols || Goulding)" Date: Wed, 12 Oct 2016 11:44:54 -0400 Subject: [PATCH 37/53] Fix section headers --- src/ch09-01-unrecoverable-errors-with-panic.md | 2 +- src/ch09-02-recoverable-errors-with-result.md | 8 ++++---- src/ch09-03-to-panic-or-not-to-panic.md | 2 ++ 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/ch09-01-unrecoverable-errors-with-panic.md b/src/ch09-01-unrecoverable-errors-with-panic.md index e8514ac76e..532f4183cb 100644 --- a/src/ch09-01-unrecoverable-errors-with-panic.md +++ b/src/ch09-01-unrecoverable-errors-with-panic.md @@ -1,4 +1,4 @@ -# Unrecoverable errors with panic! +## Unrecoverable errors with panic! Sometimes, bad things happen, and there's nothing that you can do about it. For these cases, Rust has a macro, `panic!`. When this macro executes, your program diff --git a/src/ch09-02-recoverable-errors-with-result.md b/src/ch09-02-recoverable-errors-with-result.md index 890b82d1fa..d917516cd4 100644 --- a/src/ch09-02-recoverable-errors-with-result.md +++ b/src/ch09-02-recoverable-errors-with-result.md @@ -1,4 +1,4 @@ -# Recoverable errors with `Result` +## Recoverable errors with `Result` Most errors aren't so dire. Sometimes, when a function fails, it's for a reason that we can easily interpret and respond to. As an example, maybe we are @@ -68,7 +68,7 @@ thread 'main' panicked at 'There was a problem opening the file: Error { repr: Os { code: 2, message: "No such file or directory" } }', src/main.rs:8 ``` -## Matching on Different Errors +### Matching on Different Errors There are many reasons why opening a file might fail, and we may not want to take the same actions to try to recover for all of them. For example, if the @@ -116,7 +116,7 @@ variant of the `ErrorKind` enum. Note that `File::create` could also fail, so we need to add an inner `match` statement as well! The last arm of the outer `match` stays the same to panic on any error besides the file not being found. -## Shortcuts for Panic on Error: `unwrap` and `expect` +### Shortcuts for Panic on Error: `unwrap` and `expect` Using `match` works okay but can be a bit verbose, and it doesn't always communicate intent well. The `Result` type has many helper methods @@ -167,7 +167,7 @@ examples, you might not want to muddy the code with proper error handling. But if you use them in a library, mis-using your library can cause other people's programs to halt unexpectedly, and that's not very user-friendly. -## Propagating errors with `try!` or `?` +### Propagating errors with `try!` or `?` When writing a function, if you don't want to handle the error where you are, you can return the error to the calling function. For example, here's a 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 328c30331b..c0e6701e1f 100644 --- a/src/ch09-03-to-panic-or-not-to-panic.md +++ b/src/ch09-03-to-panic-or-not-to-panic.md @@ -139,4 +139,6 @@ That way, users of our library can make sure their code uses our library properly, or at least there is an explanation for any `panic!` they get from our code. +## Summary + Now that we've reduced duplication in our validation code, let's look at a feature of Rust that helps reduce duplication in lots of code: generics! From 8f17a2a4c8b4269306ef80f19224caa50d1c45b6 Mon Sep 17 00:00:00 2001 From: "Carol (Nichols || Goulding)" Date: Wed, 12 Oct 2016 13:53:53 -0400 Subject: [PATCH 38/53] Don't talk about `main` in the default recommendation --- src/ch09-03-to-panic-or-not-to-panic.md | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) 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 c0e6701e1f..9283b0d7a3 100644 --- a/src/ch09-03-to-panic-or-not-to-panic.md +++ b/src/ch09-03-to-panic-or-not-to-panic.md @@ -1,11 +1,8 @@ ## To `panic!` or Not To `panic!` So how do you decide when you should call `panic!` and when you should return -`Result`? The most concise answer is to `panic!` in `main`, at your outermost -layer, and return `Result` everywhere else in your code. Especially when you're -writing a library for others to use, it's best to not `panic!` if at all -possible. That way, people who use your crate get to decide how they want to -handle failures from your code, instead of you deciding for them. +`Result`? A good default for a function that might fail is to return `Result` +since that gives the caller of your function the most flexibility. But that answer is simplistic. There are cases where you might want to call `panic!` in library code that have to do with Rust's quest for safety. Let's From 68bf232eb7e3c78f0c839de66f5ef436aa6ca0f5 Mon Sep 17 00:00:00 2001 From: "Carol (Nichols || Goulding)" Date: Wed, 12 Oct 2016 13:54:38 -0400 Subject: [PATCH 39/53] Remove not-quite-accurate parts of this section --- src/ch09-03-to-panic-or-not-to-panic.md | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) 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 9283b0d7a3..04c820c8a4 100644 --- a/src/ch09-03-to-panic-or-not-to-panic.md +++ b/src/ch09-03-to-panic-or-not-to-panic.md @@ -13,23 +13,20 @@ look at some more nuanced guidelines. `panic!` when your code is in a situation where it's possible to be in a bad state and: -* The cause of the bad state is not your code, it's caused by code that's - calling your code or code that your code is calling that's out of your control * The bad state is not something that's *expected* to happen occasionally * Your code after this point needs to rely on not being in this bad state * There's not a good way to encode this information in the types you use -Taking these in turn: - -A bad state consists of things like invalid values, contradictory values, or +By *bad state*, we mean some assumption, guarantee, contract, or invariant has +been broken. Some examples are invalid values, contradictory values, or nothing when you expect to have something. If someone calls your code and passes in values that don't make sense, the best thing might be to `panic!` and alert the person using your library to the bug in their code so that they can fix it during development. Similarly, `panic!` is often appropriate if you call someone else's code that is out of your control, and it returns an invalid -state that you have no way of fixing. Getting null pointers back from calling -functions in C is an example of this situation. If the only place the bug could -possibly come from is your own code, however, you should fix your bug! +state that you have no way of fixing. + +Taking the bullet points in turn: Some bad states are expected to happen sometimes, and will happen no matter how well you write your code. Example of this include a parser being given From 261056e39f69c6d330f2c63ae16dc531488d73c1 Mon Sep 17 00:00:00 2001 From: "Carol (Nichols || Goulding)" Date: Wed, 12 Oct 2016 13:55:14 -0400 Subject: [PATCH 40/53] Start of a section on good `unwrap` usage` --- src/ch09-03-to-panic-or-not-to-panic.md | 5 +++++ 1 file changed, 5 insertions(+) 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 04c820c8a4..f7eec3a960 100644 --- a/src/ch09-03-to-panic-or-not-to-panic.md +++ b/src/ch09-03-to-panic-or-not-to-panic.md @@ -57,6 +57,11 @@ don't have to have an explicit check to make sure. Another example is using an unsigned integer type like `u32`, which ensures the argument value is never negative. +### When `unwrap` is Appropriate + +It's fine to call unwrap when you know for sure that you have Some rather than +None, but haven't been able to convey that to the type system. + ### Creating Custom Types for Validation Going a step further with the idea of using Rust's type system to ensure we From 9cb2218b4641388bfd0d46ec93aea110e973c997 Mon Sep 17 00:00:00 2001 From: "Carol (Nichols || Goulding)" Date: Wed, 12 Oct 2016 13:55:30 -0400 Subject: [PATCH 41/53] Remove a "this" that's not clear --- src/ch09-03-to-panic-or-not-to-panic.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 f7eec3a960..b8bb698e5a 100644 --- a/src/ch09-03-to-panic-or-not-to-panic.md +++ b/src/ch09-03-to-panic-or-not-to-panic.md @@ -65,8 +65,8 @@ None, but haven't been able to convey that to the type system. ### Creating Custom Types for Validation Going a step further with the idea of using Rust's type system to ensure we -have a valid value, let's look at an example of creating a custom type in this -situation. Recall the guessing game in Chapter 2, where our code asked the user +have a valid value, let's look at an example of creating a custom type for +validation. Recall the guessing game in Chapter 2, where our code asked the user to guess a number between 1 and 100. We actually never validated that the user's guess was between those numbers before checking it against our secret number, only that it was positive. In this case, the consequences were not very From b7a9b8a3d0378c2ac473d20def22355a8d3a5bc2 Mon Sep 17 00:00:00 2001 From: "Carol (Nichols || Goulding)" Date: Thu, 13 Oct 2016 15:02:29 -0400 Subject: [PATCH 42/53] Flesh out constructor contract example --- src/ch09-03-to-panic-or-not-to-panic.md | 42 +++++++++++++++---------- 1 file changed, 26 insertions(+), 16 deletions(-) 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 b8bb698e5a..4a5d8780e9 100644 --- a/src/ch09-03-to-panic-or-not-to-panic.md +++ b/src/ch09-03-to-panic-or-not-to-panic.md @@ -29,7 +29,7 @@ state that you have no way of fixing. Taking the bullet points in turn: Some bad states are expected to happen sometimes, and will happen no matter how -well you write your code. Example of this include a parser being given +well you write your code. Examples of this include a parser being given malformed data to parse, or an HTTP request returning a status that indicates you have hit a rate limit. In these cases, you should indicate that failure is an expected possibility by returning a `Result` and propagate these bad states @@ -42,9 +42,14 @@ mostly for safety reasons: attempting to operate on invalid data can expose your code to vulnerabilities. This is the main reason that the standard library will `panic!` if you attempt an out-of-bounds array access: trying to access memory that doesn't belong to the current data structure is a common security -problem. A less important reason is that it makes your code more organized to -have the error checking first and the rest of the logic afterwards, rather than -interspersing error checks in with your logic. +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 is not a kind of error you want callers 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 cause a `panic`, should be explained +in the API documentation for the function. Having lots of error checks in all of your functions would be verbose and annoying, though. Luckily, our last guideline has a tip for this situation: use @@ -57,11 +62,6 @@ don't have to have an explicit check to make sure. Another example is using an unsigned integer type like `u32`, which ensures the argument value is never negative. -### When `unwrap` is Appropriate - -It's fine to call unwrap when you know for sure that you have Some rather than -None, but haven't been able to convey that to the type system. - ### Creating Custom Types for Validation Going a step further with the idea of using Rust's type system to ensure we @@ -116,7 +116,7 @@ struct Guess { } impl Guess { - fn new(value: u32) -> Guess { + pub fn new(value: u32) -> Guess { if value < 1 || value > 100 { panic!("Guess value must be between 1 and 100, got {}.", value); } @@ -124,19 +124,29 @@ impl Guess { value: value, } } + + pub fn value(&self) -> u32 { + self.value + } } ``` +Important to note is the `value` field of the `Guess` struct is private, so +code using this struct may not set that value directly. Callers *must* use the +`Guess::new` constructor function to create an instance of `Guess`, and they +may read the value using the public `value` function, but they may not access +the field directly. This means any created instance of `Guess` that does not +cause a `panic!` when `new` is called is guaranteed to return numbers between 1 +and 100 from its `value` function. + A function that takes as an argument or returns only numbers between 1 and 100 could then declare in its signature to take a `Guess` rather than a `u32`, and would not need to do any additional checks in its body. -One last guideline: since users of our library will expect `panic!` to be rare, -if we decide a library function fits these guidelines and should call `panic!`, -it's a good idea to document which functions `panic!` and in what conditions. -That way, users of our library can make sure their code uses our library -properly, or at least there is an explanation for any `panic!` they get from -our code. +### When `unwrap` is Appropriate + +It's fine to call unwrap when you know for sure that you have Some rather than +None, but haven't been able to convey that to the type system. ## Summary From c00340ce75350dc955159ebdf025d0f033e8706a Mon Sep 17 00:00:00 2001 From: "Carol (Nichols || Goulding)" Date: Mon, 17 Oct 2016 13:15:01 -0400 Subject: [PATCH 43/53] Small tweaks to wording on another readthrough --- ...ch09-01-unrecoverable-errors-with-panic.md | 30 +++++++------- src/ch09-02-recoverable-errors-with-result.md | 41 +++++++++++-------- src/ch09-03-to-panic-or-not-to-panic.md | 2 +- 3 files changed, 39 insertions(+), 34 deletions(-) diff --git a/src/ch09-01-unrecoverable-errors-with-panic.md b/src/ch09-01-unrecoverable-errors-with-panic.md index 532f4183cb..3135798ee3 100644 --- a/src/ch09-01-unrecoverable-errors-with-panic.md +++ b/src/ch09-01-unrecoverable-errors-with-panic.md @@ -46,7 +46,7 @@ message and the place in our source code where the panic occurred: `src/main.rs`, line two. But that only shows us the exact line that called `panic!`. That's not always -useful. Let's modify our example slightly to see what it's like when a `panic!` +useful. Let's look at another example to see what it's like when a `panic!` call comes from code we call instead of from our code directly: ```rust,should_panic @@ -57,10 +57,10 @@ fn main() { } ``` -We attempt to access the hundredth element of our vector, but it only has three -elements. In this situation, Rust will panic. Using `[]` is supposed to return -an element. If you pass `[]` an invalid index, though, there's no element that -Rust could return here that would be correct. +We're attempting to access the hundredth element of our vector, but it only has +three elements. In this situation, Rust will panic. Using `[]` is supposed to +return an element. If you pass `[]` an invalid index, though, there's no +element that Rust could return here that would be correct. Other languages like C will attempt to give you exactly what you asked for in this situation, even though it isn't what you want: you'll get whatever is at @@ -85,10 +85,10 @@ note: Run with `RUST_BACKTRACE=1` for a backtrace. error: Process didn't exit successfully: `target/debug/panic` (exit code: 101) ``` -This points at a file we didn't write, `../src/libcollections/vec.rs`, line -1265. That's the implementation of `Vec` in the standard library. While it's -easy to see in this short program where the error was, it would be nicer if we -could have Rust tell us what line in our program caused the error. +This points at a file we didn't write, `../src/libcollections/vec.rs`. That's +the implementation of `Vec` in the standard library. While it's easy to see +in this short program where the error was, it would be nicer if we could have +Rust tell us what line in our program caused the error. That's what the next line, the `note` is about. If we set the `RUST_BACKTRACE` environment variable, we'll get a backtrace of exactly how the error happend. @@ -128,12 +128,12 @@ core..ops..Index>::index::hb9f10d3dadbe8101 error: Process didn't exit successfully: `target/debug/panic` (exit code: 101) ``` -That's a lot of output! Line `11` there has the line in our project: -`src/main.rs` line four. The key to reading the backtrace is to start from the -top and read until we see files that we wrote: that's where the problem -originated. If we didn't want our program to panic here, this line is where we -would start investigating in order to figure out how we got to this location -with values that cause the panic. +That's a lot of output! Line 11 of the backtrace points to the line in our +project causing the problem: `src/main.rs` line four. The key to reading the +backtrace is to start from the top and read until we see files that we wrote: +that's where the problem originated. If we didn't want our program to panic +here, this line is where we would start investigating in order to figure out +how we got to this location with values that caused the panic. Now that we've covered how to `panic!` to stop our code's execution and how to debug a `panic!`, let's look at how to instead return and use recoverable diff --git a/src/ch09-02-recoverable-errors-with-result.md b/src/ch09-02-recoverable-errors-with-result.md index d917516cd4..128277976c 100644 --- a/src/ch09-02-recoverable-errors-with-result.md +++ b/src/ch09-02-recoverable-errors-with-result.md @@ -1,10 +1,10 @@ ## Recoverable errors with `Result` Most errors aren't so dire. Sometimes, when a function fails, it's for a reason -that we can easily interpret and respond to. As an example, maybe we are -making a request to a website, but it's down for maintenance. In this -situation, we'd like to wait and then try again. Terminating our process isn't -the right thing to do here. +that we can easily interpret and respond to. As an example, maybe we are making +a request to a website, but it's down for maintenance. In this situation, we'd +like to wait and then try again. Terminating our process isn't the right thing +to do here. In these cases, Rust's standard library provides an `enum` to use as the return type of the function: @@ -25,8 +25,6 @@ in more detail in Chapter XX. What you need to know for right now is that the `T` that is what we want to return in the success case, and any type `E` that is what we want to return in the error case. -As an example, let's try opening a file: - ```rust use std::fs::File; @@ -35,11 +33,11 @@ fn main() { } ``` -The `open` function returns a `Result`: there are many ways in which opening -a file can fail. For example, unless we created `hello.txt`, this file does -not yet exist. Before we can do anything with our `File`, we need to extract -it out of the result. Let's start with a basic tool: `match`. We've used it -to deal with enums previously. +The `open` function returns a `Result`: there are many ways in which opening a +file can fail. For example, unless we created `hello.txt`, this file does not +yet exist. Before we can do anything with our `File`, we need to extract it out +of the result. Let's start with a basic tool: the `match` expression that we +learned about in Chapter 6. @@ -85,7 +83,7 @@ value that we can use to handle different causes of an `Err` returned from [ioerror]: ../std/io/struct.Error.html [iokind]: ../std/io/enum.ErrorKind.html -```rust,should_panic +```rust,ignore use std::fs::File; use std::io::ErrorKind; @@ -106,7 +104,7 @@ error), } ``` - + This example uses a *match guard* with the second arm's pattern to add a condition that further refines the pattern. The `ref` in the pattern is needed @@ -230,7 +228,14 @@ Or like this using the question mark operator: +before the book's publication. + +In order to run the code examples that have the `#![feature(question_mark)]` +line, you'll need to install a nightly version of the Rust compiler. Again, +readers of the book won't need to do this since we expect the question mark +feature to stabilize before publication. + +/Carol --> ```rust #![feature(question_mark)] @@ -304,10 +309,10 @@ error[E0308]: mismatched types ``` The mismatched types that this error is pointing out says the `main()` function -has a return type of `()`, but the `try!` macro is trying to return a `Result`. -So in functions that don't return `Result`, when you call other functions that -return `Result`, you'll need to use a `match` or one of the methods on `Result` -to handle it instead of using `try!` or `?`. +has a return type of `()`, but the `try!` macro might return a `Result`. So in +functions that don't return `Result`, when you call other functions that return +`Result`, you'll need to use a `match` or one of the methods on `Result` to +handle it instead of using `try!` or `?`. 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 in which cases. 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 4a5d8780e9..c17d7e9f27 100644 --- a/src/ch09-03-to-panic-or-not-to-panic.md +++ b/src/ch09-03-to-panic-or-not-to-panic.md @@ -26,7 +26,7 @@ fix it during development. Similarly, `panic!` is often appropriate if you call someone else's code that is out of your control, and it returns an invalid state that you have no way of fixing. -Taking the bullet points in turn: +Taking each point in turn: Some bad states are expected to happen sometimes, and will happen no matter how well you write your code. Examples of this include a parser being given From 6e7eb0e87c056415a8543c761b453e727682036b Mon Sep 17 00:00:00 2001 From: "Carol (Nichols || Goulding)" Date: Mon, 17 Oct 2016 14:07:55 -0400 Subject: [PATCH 44/53] Discuss that it's ok to `unwrap` if you know it can't fail --- src/ch09-02-recoverable-errors-with-result.md | 17 +++++++++++++++++ src/ch09-03-to-panic-or-not-to-panic.md | 8 ++------ 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/src/ch09-02-recoverable-errors-with-result.md b/src/ch09-02-recoverable-errors-with-result.md index 128277976c..67c3dcfb75 100644 --- a/src/ch09-02-recoverable-errors-with-result.md +++ b/src/ch09-02-recoverable-errors-with-result.md @@ -165,6 +165,23 @@ examples, you might not want to muddy the code with proper error handling. But if you use them in a library, mis-using your library can cause other people's programs to halt unexpectedly, and that's not very user-friendly. +Another time it's appropriate to call `unwrap` is when we have some other logic +that ensures the `Result` will have an `Ok` value, but the logic isn't +something the compiler understands. If you can ensure by manually inspecting +the code that you'll never have an `Err` variant, it is perfectly acceptable to +call `unwrap`. Here's an example: + +```rust +use std::net::IpAddr; +let home = "127.0.0.1".parse::().unwrap(); +``` + +We're creating an `IpAddr` instance by parsing a hardcoded string. We can see +that `"127.0.0.1"` is a valid IP address, so it's acceptable to use `unwrap` +here. If we got the IP address string from a user of our program instead of +hardcoding this value, we'd definitely want to handle the `Result` in a more +robust way instead. + ### Propagating errors with `try!` or `?` When writing a function, if you don't want to handle the error where you are, 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 c17d7e9f27..9929cb0b1a 100644 --- a/src/ch09-03-to-panic-or-not-to-panic.md +++ b/src/ch09-03-to-panic-or-not-to-panic.md @@ -143,11 +143,7 @@ A function that takes as an argument or returns only numbers between 1 and 100 could then declare in its signature to take a `Guess` rather than a `u32`, and would not need to do any additional checks in its body. -### When `unwrap` is Appropriate - -It's fine to call unwrap when you know for sure that you have Some rather than -None, but haven't been able to convey that to the type system. - ## Summary -Now that we've reduced duplication in our validation code, let's look at a feature of Rust that helps reduce duplication in lots of code: generics! +Now that we've reduced duplication in our validation code, let's look at a +feature of Rust that helps reduce duplication in lots of code: generics! From 211aec84b9bc4f44fc84ee85232c2848e8449a10 Mon Sep 17 00:00:00 2001 From: "Carol (Nichols || Goulding)" Date: Mon, 17 Oct 2016 15:51:28 -0400 Subject: [PATCH 45/53] Add listing numbers for long or referred-to code --- ...ch09-01-unrecoverable-errors-with-panic.md | 7 +- src/ch09-02-recoverable-errors-with-result.md | 68 +++++++++++++------ src/ch09-03-to-panic-or-not-to-panic.md | 6 +- 3 files changed, 59 insertions(+), 22 deletions(-) diff --git a/src/ch09-01-unrecoverable-errors-with-panic.md b/src/ch09-01-unrecoverable-errors-with-panic.md index 3135798ee3..8ddb30f3cf 100644 --- a/src/ch09-01-unrecoverable-errors-with-panic.md +++ b/src/ch09-01-unrecoverable-errors-with-panic.md @@ -92,7 +92,7 @@ Rust tell us what line in our program caused the error. That's what the next line, the `note` is about. If we set the `RUST_BACKTRACE` environment variable, we'll get a backtrace of exactly how the error happend. -Let's try that: +Let's try that. Listing 9-1 shows the output: ```bash $ RUST_BACKTRACE=1 cargo run @@ -128,6 +128,11 @@ core..ops..Index>::index::hb9f10d3dadbe8101 error: Process didn't exit successfully: `target/debug/panic` (exit code: 101) ``` + +Listing 9-1: The backtrace generated by a call to `panic!` displayed when +the environment variable `RUST_BACKTRACE` is set + + That's a lot of output! Line 11 of the backtrace points to the line in our project causing the problem: `src/main.rs` line four. The key to reading the backtrace is to start from the top and read until we see files that we wrote: diff --git a/src/ch09-02-recoverable-errors-with-result.md b/src/ch09-02-recoverable-errors-with-result.md index 67c3dcfb75..3921de2e5a 100644 --- a/src/ch09-02-recoverable-errors-with-result.md +++ b/src/ch09-02-recoverable-errors-with-result.md @@ -25,6 +25,8 @@ in more detail in Chapter XX. What you need to know for right now is that the `T` that is what we want to return in the success case, and any type `E` that is what we want to return in the error case. +Listing 9-2 shows an example of something that might fail: opening a file. + ```rust use std::fs::File; @@ -33,11 +35,15 @@ fn main() { } ``` -The `open` function returns a `Result`: there are many ways in which opening a -file can fail. For example, unless we created `hello.txt`, this file does not -yet exist. Before we can do anything with our `File`, we need to extract it out -of the result. Let's start with a basic tool: the `match` expression that we -learned about in Chapter 6. + +Listing 9-2: Opening a file + + +The type of `f` in this example is a `Result`, because there are many ways in +which opening a file can fail. For example, unless we created `hello.txt`, this +file does not yet exist. Before we can do anything with our `File`, we need to +extract it out of the result. Listing 9-3 shows one way to handle the `Result` +with a basic tool: the `match` expression that we learned about in Chapter 6. @@ -55,6 +61,10 @@ error), } ``` + +Listing 9-3: Using a `match` expression to handle the `Result` variants we might have + + If we see an `Ok`, we can return the inner `file` out of the `Ok` variant. If we see `Err`, we have to decide what to do with it. The simplest thing is to turn our error into a `panic!` instead, by calling the macro. And since we @@ -78,7 +88,7 @@ The `Err` type `File::open` returns is [`io::Error`][ioerror], which is a struct provided by the standard library. This struct has a method `kind` that we can call to get an [`io::ErrorKind`][iokind] value that we can use to handle different causes of an `Err` returned from -`File::open` differently: +`File::open` differently as in Listing 9-4: [ioerror]: ../std/io/struct.Error.html [iokind]: ../std/io/enum.ErrorKind.html @@ -104,6 +114,10 @@ error), } ``` + +Listing 9-4: Handling different kinds of errors in different ways + + This example uses a *match guard* with the second arm's pattern to add a @@ -131,13 +145,13 @@ fn main() { } ``` -This has similar behavior as our previous example: If the call to `open()` -returns `Ok`, return the value inside. If it's an `Err`, panic. +This has similar behavior as the example using `match` in Listing 9-3: If the +call to `open()` returns `Ok`, return the value inside. If it's an `Err`, panic. -There's also another method, similar to `unwrap()`, but that lets us choose the -error message: `expect()`. Using `expect()` instead of `unwrap()` and providing -good error messages can convey your intent and make tracking down the source of -a panic easier. `expect()` looks like this: +There's also another method that is similar to `unwrap()`, but lets us choose +the error message: `expect()`. Using `expect()` instead of `unwrap()` and +providing good error messages can convey your intent and make tracking down the +source of a panic easier. `expect()` looks like this: @@ -185,10 +199,10 @@ robust way instead. ### Propagating errors with `try!` or `?` When writing a function, if you don't want to handle the error where you are, -you can return the error to the calling function. For example, here's 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 this -function: +you can return the error to the calling function. For example, Listing 9-5 +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 this function: ```rust # use std::fs::File; @@ -212,6 +226,10 @@ fn read_username_from_file() -> Result { } ``` + +Listing 9-5: A function that returns errors to the calling code using `match` + + Since the `Result` type has two type parameters, we need to include them both in our function signature. In this case, `File::open` and `read_to_string` return `std::io::Error` as the value inside the `Err` variant, so we will also @@ -220,9 +238,12 @@ username as a `String` inside the `Ok` variant, so that is our success type. This is a very common way of handling errors: propagate them upward until you're ready to deal with them. This pattern is so common in Rust that there is -a macro for it, `try!`, and as of Rust 1.XX, dedicated syntax for it: the -question mark operator. We could have written the above like this using the -`try!` macro and it would have the same functionality as the `match` expressions: +a macro for it, `try!`, and as of Rust 1.XX , dedicated syntax for it: the question mark +operator. We could have written the code in Listing 9-5 using the `try!` macro, +as in Listing 9-6, and it would have the same functionality as the `match` +expressions: @@ -240,7 +261,11 @@ fn read_username_from_file() -> Result { } ``` -Or like this using the question mark operator: + +Listing 9-6: A function that returns errors to the calling code using `try!` + + +Or as in Listing 9-7, which uses the question mark operator: From 0cd5b91fec6ce7e68313acee5500b85fddf379c0 Mon Sep 17 00:00:00 2001 From: "Carol (Nichols || Goulding)" Date: Tue, 18 Oct 2016 18:30:26 -0400 Subject: [PATCH 50/53] Spelling --- src/ch09-02-recoverable-errors-with-result.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ch09-02-recoverable-errors-with-result.md b/src/ch09-02-recoverable-errors-with-result.md index 80ed756894..e2e847589e 100644 --- a/src/ch09-02-recoverable-errors-with-result.md +++ b/src/ch09-02-recoverable-errors-with-result.md @@ -174,7 +174,7 @@ The Rust community has a love/hate relationship with `unwrap()` and `expect()`. They're very handy when prototyping, before you're ready to decide how to handle errors, and in that case they leave clear markers to look for when you are ready to make your program more robust. They're useful in tests since they -will cause the test to fail if there's an error anyplace you call them. In +will cause the test to fail if there's an error any place you call them. In examples, you might not want to muddy the code with proper error handling. But if you use them in a library, mis-using your library can cause other people's programs to halt unexpectedly, and that's not very user-friendly. From ee823778b1ceaa8fdd4c9c3016da9c7d84e700ad Mon Sep 17 00:00:00 2001 From: "Carol (Nichols || Goulding)" Date: Tue, 18 Oct 2016 18:30:35 -0400 Subject: [PATCH 51/53] Whitespace in code --- src/ch09-02-recoverable-errors-with-result.md | 5 +++++ src/ch09-03-to-panic-or-not-to-panic.md | 1 + 2 files changed, 6 insertions(+) diff --git a/src/ch09-02-recoverable-errors-with-result.md b/src/ch09-02-recoverable-errors-with-result.md index e2e847589e..e5dfc2f7c4 100644 --- a/src/ch09-02-recoverable-errors-with-result.md +++ b/src/ch09-02-recoverable-errors-with-result.md @@ -187,6 +187,7 @@ call `unwrap`. Here's an example: ```rust use std::net::IpAddr; + let home = "127.0.0.1".parse::().unwrap(); ``` @@ -256,7 +257,9 @@ expressions: fn read_username_from_file() -> Result { let mut f = try!(File::open("hello.txt")); let mut s = String::new(); + try!(f.read_to_string(&mut s)); + Ok(s) } ``` @@ -316,7 +319,9 @@ by instead doing: # fn read_username_from_file() -> Result { let mut s = String::new(); + File::open("hello.txt")?.read_to_string(&mut s)?; + Ok(s) } ``` 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 45c9e0f547..11612bb07b 100644 --- a/src/ch09-03-to-panic-or-not-to-panic.md +++ b/src/ch09-03-to-panic-or-not-to-panic.md @@ -120,6 +120,7 @@ impl Guess { if value < 1 || value > 100 { panic!("Guess value must be between 1 and 100, got {}.", value); } + Guess { value: value, } From 234e75847a43f0bb0bb03d15ae576a36aed0e20f Mon Sep 17 00:00:00 2001 From: "Carol (Nichols || Goulding)" Date: Tue, 18 Oct 2016 18:33:35 -0400 Subject: [PATCH 52/53] Make notes since we now know 1.14 will have the question mark --- .travis.yml | 4 ++-- src/ch09-02-recoverable-errors-with-result.md | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index afe0575191..9d81f13e36 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,9 +4,9 @@ cache: cargo rust: - nightly # - beta -# ^ to be changed once question_mark is in beta +# ^ to be changed once question_mark is in beta: 1.14 # - stable -# ^ to be changed once question_mark becomes stable +# ^ to be changed once question_mark becomes stable: 1.14 before_script: - (cargo install mdbook || true) script: diff --git a/src/ch09-02-recoverable-errors-with-result.md b/src/ch09-02-recoverable-errors-with-result.md index e5dfc2f7c4..a898a92939 100644 --- a/src/ch09-02-recoverable-errors-with-result.md +++ b/src/ch09-02-recoverable-errors-with-result.md @@ -239,9 +239,9 @@ username as a `String` inside the `Ok` variant, so that is our success type. This is a very common way of handling errors: propagate them upward until you're ready to deal with them. This pattern is so common in Rust that there is -a macro for it, `try!`, and as of Rust 1.XX , dedicated syntax for it: the question mark +a macro for it, `try!`, and as of Rust 1.14 , dedicated syntax for it: the question mark operator. We could have written the code in Listing 9-5 using the `try!` macro, as in Listing 9-6, and it would have the same functionality as the `match` expressions: From 3f72a067e54a373ccfc2d8dd945d3f84a315cf1f Mon Sep 17 00:00:00 2001 From: "Carol (Nichols || Goulding)" Date: Tue, 18 Oct 2016 18:37:03 -0400 Subject: [PATCH 53/53] Forward reference to api docs --- src/ch09-03-to-panic-or-not-to-panic.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 11612bb07b..94e38cea7f 100644 --- a/src/ch09-03-to-panic-or-not-to-panic.md +++ b/src/ch09-03-to-panic-or-not-to-panic.md @@ -140,7 +140,8 @@ If code calling `Guess::new` passed in a value that was not between 1 and 100, that would be a violation of the contract that `Guess::new` is relying on. This function needs to signal to the calling code that it has a bug somewhere leading to the contract violation. The conditions in which `Guess::new` might -panic should be discussed in its public-facing API documentation. +panic should be discussed in its public-facing API documentation, which we will +cover in Chapter XX. Important to note is the `value` field of the `Guess` struct is private, so code using this struct may not set that value directly. Callers *must* use the