Skip to content

Commit 16b9f67

Browse files
committed
Error handling guide
1 parent 0e6d97a commit 16b9f67

File tree

2 files changed

+225
-46
lines changed

2 files changed

+225
-46
lines changed

src/doc/guide-error-handling.md

Lines changed: 225 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,227 @@
11
% Error Handling in Rust
22

3+
> The best-laid plans of mice and men
4+
> Often go awry
5+
>
6+
> "Tae a Moose", Robert Burns
7+
8+
Sometimes, things just go wrong. It's important to have a plan for when the
9+
inevitable happens. Rust has rich support for handling errors that may (let's
10+
be honest: will) occur in your programs.
11+
12+
There are two main kinds of errors that can occur in your programs: failures,
13+
and panics. Let's talk about the difference between the two, and then discuss
14+
how to handle each. Then, we'll discuss upgrading failures to panics.
15+
16+
# Failure vs. Panic
17+
18+
Rust uses two terms to differentiate between two forms of error: failure, and
19+
panic. A **failure** is an error that can be recovered from in some way. A
20+
**panic** is an error that cannot be recovered from.
21+
22+
What do we mean by 'recover'? Well, in most cases, the possibility of an error
23+
is expected. For example, consider the `from_str` function:
24+
25+
```{rust,ignore}
26+
from_str("5");
27+
```
28+
29+
This function takes a string argument and converts it into another type. But
30+
because it's a string, you can't be sure that the conversion actually works.
31+
For example, what should this convert to?
32+
33+
```{rust,ignore}
34+
from_str("hello5world");
35+
```
36+
37+
This won't work. So we know that this function will only work properly for some
38+
inputs. It's expected behavior. We call this kind of error 'failure.'
39+
40+
On the other hand, sometimes, there are errors that are unexpected, or which
41+
we cannot recover from. A classic example is an `assert!`:
42+
43+
```{rust,ignore}
44+
assert!(x == 5);
45+
```
46+
47+
We use `assert!` to declare that something is true. If it's not true, something
48+
is very wrong. Wrong enough that we can't continue with things in the current
49+
state. Another example is using the `unreachable!()` macro
50+
51+
```{rust,ignore}
52+
enum Event {
53+
NewRelease,
54+
}
55+
56+
fn probability(_: &Event) -> f64 {
57+
// real implementation would be more complex, of course
58+
0.95
59+
}
60+
61+
fn descriptive_probability(event: Event) -> &'static str {
62+
match probability(&event) {
63+
1.00 => "certain",
64+
0.00 => "impossible",
65+
0.00 ... 0.25 => "very unlikely",
66+
0.25 ... 0.50 => "unlikely",
67+
0.50 ... 0.75 => "likely",
68+
0.75 ... 1.00 => "very likely",
69+
}
70+
}
71+
72+
fn main() {
73+
std::io::println(descriptive_probability(NewRelease));
74+
}
75+
```
76+
77+
This will give us an error:
78+
79+
```{notrust,ignore}
80+
error: non-exhaustive patterns: `_` not covered [E0004]
81+
```
82+
83+
While we know that we've covered all possible cases, Rust can't tell. It
84+
doesn't know that probability is between 0.0 and 1.0. So we add another case:
85+
86+
```rust
87+
enum Event {
88+
NewRelease,
89+
}
90+
91+
fn probability(_: &Event) -> f64 {
92+
// real implementation would be more complex, of course
93+
0.95
94+
}
95+
96+
fn descriptive_probability(event: Event) -> &'static str {
97+
match probability(&event) {
98+
1.00 => "certain",
99+
0.00 => "impossible",
100+
0.00 ... 0.25 => "very unlikely",
101+
0.25 ... 0.50 => "unlikely",
102+
0.50 ... 0.75 => "likely",
103+
0.75 ... 1.00 => "very likely",
104+
_ => unreachable!()
105+
}
106+
}
107+
108+
fn main() {
109+
std::io::println(descriptive_probability(NewRelease));
110+
}
111+
```
112+
113+
We shouldn't ever hit the `_` case, so we use the `unreachable!()` macro to
114+
indicate this. `unreachable!()` gives a different kind of error than `Result`.
115+
Rust calls these sorts of errors 'panics.'
116+
117+
# Handling errors with `Option` and `Result`
118+
119+
The simplest way to indicate that a function may fail is to use the `Option<T>`
120+
type. Remember our `from_str()` example? Here's its type signature:
121+
122+
```{rust,ignore}
123+
pub fn from_str<A: FromStr>(s: &str) -> Option<A>
124+
```
125+
126+
`from_str()` returns an `Option<A>`. If the conversion succeeds, it will return
127+
`Some(value)`, and if it fails, it will return `None`.
128+
129+
This is appropriate for the simplest of cases, but doesn't give us a lot of
130+
information in the failure case. What if we wanted to know _why_ the conversion
131+
failed? For this, we can use the `Result<T, E>` type. It looks like this:
132+
133+
```rust
134+
enum Result<T, E> {
135+
Ok(T),
136+
Err(E)
137+
}
138+
```
139+
140+
This enum is provided by Rust itself, so you don't need to define it to use it
141+
in your code. The `Ok(T)` variant represents a success, and the `Err(E)` variant
142+
represents a failure. Returning a `Result` instead of an `Option` is recommended
143+
for all but the most trivial of situations.
144+
145+
Here's an example of using `Result`:
146+
147+
```rust
148+
#[deriving(Show)]
149+
enum Version { Version1, Version2 }
150+
151+
#[deriving(Show)]
152+
enum ParseError { InvalidHeaderLength, InvalidVersion }
153+
154+
155+
fn parse_version(header: &[u8]) -> Result<Version, ParseError> {
156+
if header.len() < 1 {
157+
return Err(InvalidHeaderLength);
158+
}
159+
match header[0] {
160+
1 => Ok(Version1),
161+
2 => Ok(Version2),
162+
_ => Err(InvalidVersion)
163+
}
164+
}
165+
166+
let version = parse_version(&[1, 2, 3, 4]);
167+
match version {
168+
Ok(v) => {
169+
println!("working with version: {}", v);
170+
}
171+
Err(e) => {
172+
println!("error parsing header: {}", e);
173+
}
174+
}
175+
```
176+
177+
This function makes use of an enum, `ParseError`, to enumerate the various
178+
errors that can occur.
179+
180+
# Non-recoverable errors with `panic!`
181+
182+
In the case of an error that is unexpected and not recoverable, the `panic!`
183+
macro will induce a panic. This will crash the current task, and give an error:
184+
185+
```{rust,ignore}
186+
panic!("boom");
187+
```
188+
189+
gives
190+
191+
```{notrust,ignore}
192+
task '<main>' panicked at 'boom', hello.rs:2
193+
```
194+
195+
when you run it.
196+
197+
Because these kinds of situations are relatively rare, use panics sparingly.
198+
199+
# Upgrading failures to panics
200+
201+
In certain circumstances, even though a function may fail, we may want to treat
202+
it as a panic instead. For example, `io::stdin().read_line()` returns an
203+
`IoResult<String>`, a form of `Result`, when there is an error reading the
204+
line. This allows us to handle and possibly recover from this sort of error.
205+
206+
If we don't want to handle this error, and would rather just abort the program,
207+
we can use the `unwrap()` method:
208+
209+
```{rust,ignore}
210+
io::stdin().read_line().unwrap();
211+
```
212+
213+
`unwrap()` will `panic!` if the `Option` is `None`. This basically says "Give
214+
me the value, and if something goes wrong, just crash." This is less reliable
215+
than matching the error and attempting to recover, but is also significantly
216+
shorter. Sometimes, just crashing is appropriate.
217+
218+
There's another way of doing this that's a bit nicer than `unwrap()`:
219+
220+
```{rust,ignore}
221+
let input = io::stdin().read_line()
222+
.ok()
223+
.expect("Failed to read line");
224+
```
225+
`ok()` converts the `IoResult` into an `Option`, and `expect()` does the same
226+
thing as `unwrap()`, but takes a message. This message is passed along to the
227+
underlying `panic!`, providing a better error message if the code errors.

src/libcore/result.rs

Lines changed: 0 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -227,52 +227,6 @@
227227
//! ```
228228
//!
229229
//! `try!` is imported by the prelude, and is available everywhere.
230-
//!
231-
//! # `Result` and `Option`
232-
//!
233-
//! The `Result` and [`Option`](../option/index.html) types are
234-
//! similar and complementary: they are often employed to indicate a
235-
//! lack of a return value; and they are trivially converted between
236-
//! each other, so `Result`s are often handled by first converting to
237-
//! `Option` with the [`ok`](type.Result.html#method.ok) and
238-
//! [`err`](type.Result.html#method.ok) methods.
239-
//!
240-
//! Whereas `Option` only indicates the lack of a value, `Result` is
241-
//! specifically for error reporting, and carries with it an error
242-
//! value. Sometimes `Option` is used for indicating errors, but this
243-
//! is only for simple cases and is generally discouraged. Even when
244-
//! there is no useful error value to return, prefer `Result<T, ()>`.
245-
//!
246-
//! Converting to an `Option` with `ok()` to handle an error:
247-
//!
248-
//! ```
249-
//! use std::io::Timer;
250-
//! let mut t = Timer::new().ok().expect("failed to create timer!");
251-
//! ```
252-
//!
253-
//! # `Result` vs. `panic!`
254-
//!
255-
//! `Result` is for recoverable errors; `panic!` is for unrecoverable
256-
//! errors. Callers should always be able to avoid panics if they
257-
//! take the proper precautions, for example, calling `is_some()`
258-
//! on an `Option` type before calling `unwrap`.
259-
//!
260-
//! The suitability of `panic!` as an error handling mechanism is
261-
//! limited by Rust's lack of any way to "catch" and resume execution
262-
//! from a thrown exception. Therefore using panics for error
263-
//! handling requires encapsulating code that may panic in a task.
264-
//! Calling the `panic!` macro, or invoking `panic!` indirectly should be
265-
//! avoided as an error reporting strategy. Panics is only for
266-
//! unrecoverable errors and a panicking task is typically the sign of
267-
//! a bug.
268-
//!
269-
//! A module that instead returns `Results` is alerting the caller
270-
//! that failure is possible, and providing precise control over how
271-
//! it is handled.
272-
//!
273-
//! Furthermore, panics may not be recoverable at all, depending on
274-
//! the context. The caller of `panic!` should assume that execution
275-
//! will not resume after the panic, that a panic is catastrophic.
276230
277231
#![stable]
278232

0 commit comments

Comments
 (0)