Skip to content

Commit aabad34

Browse files
Merge pull request #134 from rust-lang/error-handling
start of error handling
2 parents 5c1eed2 + 3f72a06 commit aabad34

6 files changed

+719
-2
lines changed

.travis.yml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@ language: rust
33
cache: cargo
44
rust:
55
- nightly
6-
- stable
6+
# - beta
7+
# ^ to be changed once question_mark is in beta: 1.14
8+
# - stable
9+
# ^ to be changed once question_mark becomes stable: 1.14
710
before_script:
811
- (cargo install mdbook || true)
912
script:

src/SUMMARY.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,10 @@
4040
- [Strings](ch08-02-strings.md)
4141
- [Hash Maps](ch08-03-hash-maps.md)
4242

43-
- [Error Handling]()
43+
- [Error Handling](ch09-00-error-handling.md)
44+
- [Unrecoverable Errors with `panic!`](ch09-01-unrecoverable-errors-with-panic.md)
45+
- [Recoverable Errors with `Result`](ch09-02-recoverable-errors-with-result.md)
46+
- [To `panic!` or Not To `panic!`](ch09-03-to-panic-or-not-to-panic.md)
4447

4548
- [Generics]()
4649

src/ch09-00-error-handling.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# Error Handling
2+
3+
Rust's focus on reliability extends to the area of error handling. Errors are a
4+
fact of life in software, so Rust has a number of features that you can use to
5+
handle situations in which something bad happens. In many cases, Rust requires
6+
you to acknowledge the possibility of an error occurring and take some action
7+
in that situation. This makes your program more robust by eliminating the
8+
possibility of unexpected errors only being discovered after you've deployed
9+
your code to production.
10+
11+
Rust groups errors into two major kinds: errors that are *recoverable*, and
12+
errors that are *unrecoverable*. Recoverable errors are problems like a file not
13+
being found, where it's usually reasonable to report that problem to the user
14+
and retry the operation. Unrecoverable errors are problems like trying to
15+
access a location beyond the end of an array, and these are always symptoms of
16+
bugs.
17+
18+
Most languages do not distinguish between the two kinds of errors, so they
19+
handle both kinds in the same way using mechanisms like exceptions. Rust
20+
doesn't have exceptions. Instead, it has the value `Result<T, E>` to return in
21+
the case of recoverable errors and the `panic!` macro that stops execution when
22+
it encounters unrecoverable errors. This chapter will cover the more
23+
straightforward case of calling `panic!` first. Then, we'll talk about
24+
returning `Result<T, E>` values and calling functions that return `Result<T,
25+
E>`. Finally, we'll discuss considerations to take into account when deciding
26+
whether to try to recover from an error or to stop execution.
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
## Unrecoverable Errors with `panic!`
2+
3+
Sometimes, bad things happen, and there's nothing that you can do about it. For
4+
these cases, Rust has a macro, `panic!`. When this macro executes, your program
5+
will print a failure message, unwind and clean up the stack, and then quit. The
6+
most common reason for this is when a bug of some kind has been detected, and
7+
it's not clear how to handle the error.
8+
9+
<!-- PROD: START BOX -->
10+
11+
> #### Unwinding
12+
> By default, when a `panic!` happens in Rust, the program starts
13+
> *unwinding*, which means Rust walks back up the stack and cleans up the data
14+
> from each function it encounters. Doing that walking and cleanup is a lot of
15+
> work. The alternative is to immediately `abort`, which ends the program
16+
> without cleaning up. Memory that the program was using will need to be cleaned
17+
> up by the operating system. If you're in a situation where you need to make
18+
> the resulting binary as small as possible, you can switch from unwinding on
19+
> panic to aborting on panic by adding `panic = 'abort'` to the appropriate
20+
> `[profile]` sections in your `Cargo.toml`.
21+
22+
<!-- PROD: END BOX -->
23+
24+
Let's try out calling `panic!()` with a simple program:
25+
26+
```rust,should_panic
27+
fn main() {
28+
panic!("crash and burn");
29+
}
30+
```
31+
32+
If you run it, you'll see something like this:
33+
34+
```bash
35+
$ cargo run
36+
Compiling panic v0.1.0 (file:///projects/panic)
37+
Finished debug [unoptimized + debuginfo] target(s) in 0.25 secs
38+
Running `target/debug/panic`
39+
thread 'main' panicked at 'crash and burn', src/main.rs:2
40+
note: Run with `RUST_BACKTRACE=1` for a backtrace.
41+
error: Process didn't exit successfully: `target/debug/panic` (exit code: 101)
42+
```
43+
44+
There are three lines of error message here. The first line shows our panic
45+
message and the place in our source code where the panic occurred:
46+
`src/main.rs`, line two.
47+
48+
But that only shows us the exact line that called `panic!`. That's not always
49+
useful. Let's look at another example to see what it's like when a `panic!`
50+
call comes from code we call instead of from our code directly:
51+
52+
```rust,should_panic
53+
fn main() {
54+
let v = vec![1, 2, 3];
55+
56+
v[100];
57+
}
58+
```
59+
60+
We're attempting to access the hundredth element of our vector, but it only has
61+
three elements. In this situation, Rust will panic. Using `[]` is supposed to
62+
return an element. If you pass `[]` an invalid index, though, there's no
63+
element that Rust could return here that would be correct.
64+
65+
Other languages like C will attempt to give you exactly what you asked for in
66+
this situation, even though it isn't what you want: you'll get whatever is at
67+
the location in memory that would correspond to that element in the vector,
68+
even though the memory doesn't belong to the vector. This is called a *buffer
69+
overread*, and can lead to security vulnerabilities if an attacker can
70+
manipulate the index in such a way as to read data they shouldn't be allowed to
71+
that is stored after the array.
72+
73+
In order to protect your program from this sort of vulnerability, if you try to
74+
read an element at an index that doesn't exist, Rust will stop execution and
75+
refuse to continue with an invalid value. Let's try it and see:
76+
77+
```bash
78+
$ cargo run
79+
Compiling panic v0.1.0 (file:///projects/panic)
80+
Finished debug [unoptimized + debuginfo] target(s) in 0.27 secs
81+
Running `target/debug/panic`
82+
thread 'main' panicked at 'index out of bounds: the len is 3 but the index is
83+
100', ../src/libcollections/vec.rs:1265
84+
note: Run with `RUST_BACKTRACE=1` for a backtrace.
85+
error: Process didn't exit successfully: `target/debug/panic` (exit code: 101)
86+
```
87+
88+
This points at a file we didn't write, `../src/libcollections/vec.rs`. That's
89+
the implementation of `Vec<T>` in the standard library. While it's easy to see
90+
in this short program where the error was, it would be nicer if we could have
91+
Rust tell us what line in our program caused the error.
92+
93+
That's what the next line, the `note` is about. If we set the `RUST_BACKTRACE`
94+
environment variable, we'll get a backtrace of exactly how the error happend.
95+
Let's try that. Listing 9-1 shows the output:
96+
97+
```bash
98+
$ RUST_BACKTRACE=1 cargo run
99+
Finished debug [unoptimized + debuginfo] target(s) in 0.0 secs
100+
Running `target/debug/panic`
101+
thread 'main' panicked at 'index out of bounds: the len is 3 but the index is
102+
100', ../src/libcollections/vec.rs:1265
103+
stack backtrace:
104+
1: 0x560956150ae9 -
105+
std::sys::backtrace::tracing::imp::write::h482d45d91246faa2
106+
2: 0x56095615345c -
107+
std::panicking::default_hook::_{{closure}}::h89158f66286b674e
108+
3: 0x56095615291e - std::panicking::default_hook::h9e30d428ee3b0c43
109+
4: 0x560956152f88 -
110+
std::panicking::rust_panic_with_hook::h2224f33fb7bf2f4c
111+
5: 0x560956152e22 - std::panicking::begin_panic::hcb11a4dc6d779ae5
112+
6: 0x560956152d50 - std::panicking::begin_panic_fmt::h310416c62f3935b3
113+
7: 0x560956152cd1 - rust_begin_unwind
114+
8: 0x560956188a2f - core::panicking::panic_fmt::hc5789f4e80194729
115+
9: 0x5609561889d3 -
116+
core::panicking::panic_bounds_check::hb2d969c3cc11ed08
117+
10: 0x56095614c075 - _<collections..vec..Vec<T> as
118+
core..ops..Index<usize>>::index::hb9f10d3dadbe8101
119+
at ../src/libcollections/vec.rs:1265
120+
11: 0x56095614c134 - panic::main::h2d7d3751fb8705e2
121+
at /projects/panic/src/main.rs:4
122+
12: 0x56095615af46 - __rust_maybe_catch_panic
123+
13: 0x560956152082 - std::rt::lang_start::h352a66f5026f54bd
124+
14: 0x56095614c1b3 - main
125+
15: 0x7f75b88ed72f - __libc_start_main
126+
16: 0x56095614b3c8 - _start
127+
17: 0x0 - <unknown>
128+
error: Process didn't exit successfully: `target/debug/panic` (exit code: 101)
129+
```
130+
131+
<caption>
132+
Listing 9-1: The backtrace generated by a call to `panic!` displayed when
133+
the environment variable `RUST_BACKTRACE` is set
134+
</caption>
135+
136+
That's a lot of output! Line 11 of the backtrace points to the line in our
137+
project causing the problem: `src/main.rs` line four. The key to reading the
138+
backtrace is to start from the top and read until we see files that we wrote:
139+
that's where the problem originated. If we didn't want our program to panic
140+
here, this line is where we would start investigating in order to figure out
141+
how we got to this location with values that caused the panic.
142+
143+
Now that we've covered how to `panic!` to stop our code's execution and how to
144+
debug a `panic!`, let's look at how to instead return and use recoverable
145+
errors with `Result`.

0 commit comments

Comments
 (0)