Skip to content

Commit be5258d

Browse files
committed
start of error handling
1 parent 086d7b4 commit be5258d

5 files changed

+386
-1
lines changed

src/SUMMARY.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,10 @@
3434

3535
- [Crates & Modules]()
3636

37-
- [Error Handling]()
37+
- [Error Handling](ch07-01-error-handling.md)
38+
- [Unrecoverable errors with panic!](ch07-02-unrecoverable-errors-with-panic.md)
39+
- [Recoverable errors with `Result<T, E>`](ch07-03-recoverable-errors-with-result.md)
40+
- [Creating your own Error types](ch07-04-creating-your-own-error-types.md)
3841

3942
- [Basic Collections]()
4043
- [Vectors]()

src/ch07-01-error-handling.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# Error Handling
2+
3+
Rust's laser-focus on safety spills over into a related area: error handling.
4+
Errors are a fact of life in software. Rust has a number of tools that you can
5+
use to handle things when something bad happens.
6+
7+
Rust splits errors into two major kinds: errors that are recoverable, and
8+
errors that are not recoverable. It has two different strategies to handle
9+
these two different forms of errors.
10+
11+
What does it mean to "recover" from an error? In the simplest sense, it
12+
relates to the answer of this question:
13+
14+
> If I call a function, and something bad happens, can I do something
15+
> meaningful? Or should execution stop?
16+
17+
Some kinds of problems have solutions, but with other kinds of errors,
18+
all you can do is throw up your hands and give up.
19+
20+
We'll start off our examination of error handling by talking about the
21+
unrecoverable case first. Why? You can think of unrecoverable errors as a
22+
subset of recoverable errors. If you choose to treat an error as unrecoverable,
23+
then the function that calls your code has no choice in the matter. However, if
24+
your function returns a recoverable error, the calling function has a choice:
25+
handle the error properly, or convert it into an unrecoverable error. This is
26+
one of the reasons that most Rust code chooses to treat errors as recoverable:
27+
it's more flexible. We're going to explore an example that starts off as
28+
unrecoverable, and then, in the next section, we will convert it to a
29+
convertable error. Finally, we'll talk about how to create our own custom error
30+
types.
Lines changed: 235 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,235 @@
1+
# Unrecoverable errors with panic!
2+
3+
You've already seen the way to signal an unrecoverable error: the `panic!`
4+
macro. Here's an example of using `panic!`:
5+
6+
```rust
7+
fn check_guess(number: u32) -> bool {
8+
if number > 100 {
9+
panic!("Guess was too big: {}", number);
10+
}
11+
12+
number == 34
13+
}
14+
```
15+
16+
This function accepts a guess between zero and a hundred, and checks if it's
17+
equivalent to the correct number, which is `34` in this case. It's kind of a
18+
silly function, but we need an example, so it works.
19+
20+
There's no number type for "between zero and a hundred" in Rust, so we are
21+
accepting a `u32`, and then checking in the function's body to make sure the
22+
guess is in-bounds. If the number is too big, there's been an error: someone
23+
made a mistake. We then invoke `panic!` to say "something went wrong, we cannot
24+
continue to run this program."
25+
26+
Checking some sort of condition and then `panic!`ing if something is wrong is a
27+
common use of `panic!`. In fact, there's a second macro in Rust, `assert!` for
28+
this case. `assert!` will check some kind of condition, and `panic!` if the
29+
condition is false. We could also write our function like this:
30+
31+
```rust
32+
fn check_guess(number: u32) -> bool {
33+
assert!(number < 100);
34+
35+
number == 34
36+
}
37+
```
38+
39+
If we try and use `check_guess` in a program, and make an error:
40+
41+
```rust,should_panic
42+
fn check_guess(number: u32) -> bool {
43+
assert!(number < 100);
44+
45+
number == 34
46+
}
47+
48+
fn main() {
49+
let answer = check_guess(5);
50+
println!("answer was: {}", answer);
51+
52+
let answer = check_guess(34);
53+
println!("answer was: {}", answer);
54+
55+
let answer = check_guess(500);
56+
println!("answer was: {}", answer);
57+
}
58+
```
59+
60+
We'll see output like this:
61+
62+
```text
63+
answer was: false
64+
answer was: true
65+
66+
thread '<main>' panicked at 'assertion failed: number < 100', <anon>:2
67+
```
68+
69+
First, `5` was okay, but false. Then, `34` was okay, but true. Finally, `500`
70+
caused a panic.
71+
72+
Panics cause your program to stop executing. To check this, we could move the
73+
failing case above the good cases:
74+
75+
```rust,should_panic
76+
# fn check_guess(number: u32) -> bool {
77+
# assert!(number < 100);
78+
#
79+
# number == 34
80+
# }
81+
#
82+
fn main() {
83+
let answer = check_guess(500);
84+
println!("answer was: {}", answer);
85+
86+
let answer = check_guess(5);
87+
println!("answer was: {}", answer);
88+
89+
let answer = check_guess(34);
90+
println!("answer was: {}", answer);
91+
}
92+
```
93+
94+
If we run it, we'll see that we never check `5` or `34`:
95+
96+
```text
97+
thread '<main>' panicked at 'assertion failed: number < 100', <anon>:2
98+
```
99+
100+
This is why we call `panic!` an unrecoverable error: the other code never gets a
101+
chance to run. Our program just ends.
102+
103+
But what does it mean to "end a program"? As it turns out, there are multiple
104+
strategies for processing an unrecoverable error. The two main ones are
105+
'unwinding' and 'aborting'.
106+
107+
## Unwinding
108+
109+
By default, when a `panic!` happens in Rust, it starts doing something called
110+
"unwinding". To explain unwinding, let's consider a slightly more complex
111+
program:
112+
113+
```rust,should_panic
114+
fn step1() {
115+
let s = String::from("Step 1");
116+
step2();
117+
}
118+
119+
fn step2() {
120+
let s = String::from("Step 2");
121+
step3();
122+
}
123+
124+
fn step3() {
125+
let s = String::from("Step 3");
126+
check_guess(500);
127+
}
128+
129+
fn check_guess(number: u32) -> bool {
130+
assert!(number < 100);
131+
132+
number == 34
133+
}
134+
135+
fn main() {
136+
step1();
137+
}
138+
```
139+
140+
Here, we have four functions total: `step1` calls `step2` which calls `step3`
141+
which then calls `check_guess`. Something like this diagram, with each of the
142+
variable bindings written in:
143+
144+
```text
145+
> main
146+
|
147+
> step1 String: "Step 1"
148+
|
149+
> step2 String: "Step 2"
150+
|
151+
> step3 String: "Step 3"
152+
|
153+
> check_guess: u32: 500
154+
```
155+
156+
When `check_guess` causes a `panic!` via `assert!`, it will walk back through
157+
each of these functions, and clean them up. We haven't yet talked about
158+
destructors in Rust, that'll come in Chapter XX. For now, think about it this
159+
way: simple values like that `u32` can be destroyed by freeing their memory.
160+
More complicated values, like `String`s, have more complicated needs. In these
161+
cases, there is a function that does this cleanup. We call this a "drop
162+
function" in Rust.
163+
164+
So, the first thing that will happen is that `check_guess`, our current
165+
function, gets cleaned up. There's only one value, the `500`, and so its memory
166+
will be freed. Now our program looks like this:
167+
168+
```text
169+
> main
170+
|
171+
> step1 String: "Step 1"
172+
|
173+
> step2 String: "Step 2"
174+
|
175+
> step3 String: "Step 3"
176+
```
177+
178+
Now we're on `step3`. In the same way, Rust will call the `String`'s drop
179+
function, deallocating the `String`. Once that's done, we can move on:
180+
181+
```text
182+
> main
183+
|
184+
> step1 String: "Step 1"
185+
|
186+
> step2 String: "Step 2"
187+
```
188+
189+
The pattern continues: `step2` has a single value, and `"Step 2"` has its
190+
drop function called. Next!
191+
192+
```text
193+
> main
194+
|
195+
> step1 String: "Step 1"
196+
```
197+
198+
Almost done! `step1` also has a `String`, so Rust will invoke its drop function.
199+
200+
```text
201+
> main
202+
```
203+
204+
Finally, all we have left is `main()`. It has no variable bindings or arguments
205+
in this case, so there's nothing to clean up. Our whole program has been delt
206+
with, and so terminates execution, after printing a message.
207+
208+
## Aborting
209+
210+
Doing all that is a lot of work! And the end result is that our program
211+
terminates. Handing panics with unwinding is useful in many scenarios, but some
212+
applications would rather skip straight to the end, and 'abort'. With some
213+
configuration, Cargo will allow us to use this alternate implementation of
214+
`panic!`. What happens when that's enabled? Let's consider what our call stack
215+
looked like:
216+
217+
```text
218+
> main
219+
|
220+
> step1 String: "Step 1"
221+
|
222+
> step2 String: "Step 2"
223+
|
224+
> step3 String: "Step 3"
225+
|
226+
> check_guess: u32: 500
227+
```
228+
229+
With an abort implementation of `panic!`, instead of walking back up through the
230+
previous functions and cleaning everything up, we skip straight to the end: our
231+
program terminates. But what about our resources? In the case of memory, like is
232+
the case with `String`s, the operating system will reclaim the memory. So for
233+
this program, the two cases are identical, but aborting is much more efficient.
234+
Other, more complex resources work differently, however, and so aborting may not
235+
always be the right choice either. It depends!
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
# Recoverable errors with `Result<T, E>`
2+
3+
The vast majority of errors in Rust are able to be recovered from. For this
4+
case, Rust has a special type, `Result<T, E>`, to signal that a function might
5+
succeed or fail.
6+
7+
Let's take a look at our example function from the last section:
8+
9+
```rust
10+
fn check_guess(number: u32) -> bool {
11+
if number > 100 {
12+
panic!("Guess was too big: {}", number);
13+
}
14+
15+
number == 34
16+
}
17+
```
18+
19+
We don't want the entire program to end if our `number` was incorrect. That's a
20+
bit harsh! Instead, we want to signal that an error occurred. Here's a version
21+
of `check_guess` that uses `Result<T, E>`:
22+
23+
```rust
24+
fn check_guess(number: u32) -> Result<bool, &'static str> {
25+
if number > 100 {
26+
return Err("Number was out of range");
27+
}
28+
29+
Ok(number == 34)
30+
}
31+
```
32+
33+
There are three big changes here: to the return type, to the error case, and to
34+
the non-error case. Let's look at each in turn.
35+
36+
```rust
37+
fn check_guess(number: u32) -> Result<bool, &'static str> {
38+
# if number > 100 {
39+
# return Err("Number was out of range");
40+
# }
41+
#
42+
# Ok(number == 34)
43+
# }
44+
```
45+
46+
Originally, we returned a `bool`, but now we return a
47+
`Result<bool, &'static str>`. This is a type [provided by the standard library]
48+
specifically for indicating that a function might have an error. More
49+
specifically, it's an [`enum`] that looks like this:
50+
51+
```rust
52+
pub enum Result<T, E> {
53+
Ok(T),
54+
Err(E),
55+
}
56+
```
57+
58+
[provided by the standard library]: https://doc.rust-lang.org/stable/std/result/enum.Result.html
59+
[`enum]`: ch06-01-enums.html
60+
61+
`Result<T, E>` is generic over two types: `T`, which is the successful case, and
62+
`E`, which is the error case. It has two variants, `Ok` and `Err`, which also
63+
correspond to these cases, respectively. So the type `Result<bool, &'static
64+
str>` means that in the successful, `Ok` case, we will be returning a `bool`.
65+
But in the failure, `Err` case, we will be returning a string literal.
66+
67+
```rust
68+
# fn check_guess(number: u32) -> Result<bool, &'static str> {
69+
# if number > 100 {
70+
return Err("Number was out of range");
71+
# }
72+
#
73+
# Ok(number == 34)
74+
# }
75+
```
76+
77+
The second change we need to make is to our error case. Instead of causing a
78+
`panic!`, we now `return` an `Err`, with a string literal inside. Remember,
79+
`Result<T, E>` is an enum: `Err` is one of its variants.
80+
81+
```rust
82+
# fn check_guess(number: u32) -> Result<bool, &'static str> {
83+
# if number > 100 {
84+
# return Err("Number was out of range");
85+
# }
86+
#
87+
Ok(number == 34)
88+
# }
89+
```
90+
91+
We also need to handle the successful case as well, and we make a change
92+
similarly to the error case: we wrap our return value in `Ok`, which gives it
93+
the correct type.
94+
95+
## Handling an error
96+
97+
Let's examine how to use this function:
98+
99+
```rust
100+
fn check_guess(number: u32) -> Result<bool, &'static str> {
101+
if number > 100 {
102+
return Err("Number was out of range");
103+
}
104+
105+
Ok(number == 34)
106+
}
107+
108+
fn main() {
109+
let answer = check_guess(5);
110+
111+
match answer {
112+
Ok(b) => println!("answer: {}", b),
113+
Err(e) => println!("There was an error: {}, e"),
114+
};
115+
}
116+
```
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# Creating your own Error types

0 commit comments

Comments
 (0)