Error handling guide
This commit is contained in:
parent
0e6d97aab2
commit
16b9f67bf3
@ -1,2 +1,227 @@
|
||||
% Error Handling in Rust
|
||||
|
||||
> The best-laid plans of mice and men
|
||||
> Often go awry
|
||||
>
|
||||
> "Tae a Moose", Robert Burns
|
||||
|
||||
Sometimes, things just go wrong. It's important to have a plan for when the
|
||||
inevitable happens. Rust has rich support for handling errors that may (let's
|
||||
be honest: will) occur in your programs.
|
||||
|
||||
There are two main kinds of errors that can occur in your programs: failures,
|
||||
and panics. Let's talk about the difference between the two, and then discuss
|
||||
how to handle each. Then, we'll discuss upgrading failures to panics.
|
||||
|
||||
# Failure vs. Panic
|
||||
|
||||
Rust uses two terms to differentiate between two forms of error: failure, and
|
||||
panic. A **failure** is an error that can be recovered from in some way. A
|
||||
**panic** is an error that cannot be recovered from.
|
||||
|
||||
What do we mean by 'recover'? Well, in most cases, the possibility of an error
|
||||
is expected. For example, consider the `from_str` function:
|
||||
|
||||
```{rust,ignore}
|
||||
from_str("5");
|
||||
```
|
||||
|
||||
This function takes a string argument and converts it into another type. But
|
||||
because it's a string, you can't be sure that the conversion actually works.
|
||||
For example, what should this convert to?
|
||||
|
||||
```{rust,ignore}
|
||||
from_str("hello5world");
|
||||
```
|
||||
|
||||
This won't work. So we know that this function will only work properly for some
|
||||
inputs. It's expected behavior. We call this kind of error 'failure.'
|
||||
|
||||
On the other hand, sometimes, there are errors that are unexpected, or which
|
||||
we cannot recover from. A classic example is an `assert!`:
|
||||
|
||||
```{rust,ignore}
|
||||
assert!(x == 5);
|
||||
```
|
||||
|
||||
We use `assert!` to declare that something is true. If it's not true, something
|
||||
is very wrong. Wrong enough that we can't continue with things in the current
|
||||
state. Another example is using the `unreachable!()` macro
|
||||
|
||||
```{rust,ignore}
|
||||
enum Event {
|
||||
NewRelease,
|
||||
}
|
||||
|
||||
fn probability(_: &Event) -> f64 {
|
||||
// real implementation would be more complex, of course
|
||||
0.95
|
||||
}
|
||||
|
||||
fn descriptive_probability(event: Event) -> &'static str {
|
||||
match probability(&event) {
|
||||
1.00 => "certain",
|
||||
0.00 => "impossible",
|
||||
0.00 ... 0.25 => "very unlikely",
|
||||
0.25 ... 0.50 => "unlikely",
|
||||
0.50 ... 0.75 => "likely",
|
||||
0.75 ... 1.00 => "very likely",
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
std::io::println(descriptive_probability(NewRelease));
|
||||
}
|
||||
```
|
||||
|
||||
This will give us an error:
|
||||
|
||||
```{notrust,ignore}
|
||||
error: non-exhaustive patterns: `_` not covered [E0004]
|
||||
```
|
||||
|
||||
While we know that we've covered all possible cases, Rust can't tell. It
|
||||
doesn't know that probability is between 0.0 and 1.0. So we add another case:
|
||||
|
||||
```rust
|
||||
enum Event {
|
||||
NewRelease,
|
||||
}
|
||||
|
||||
fn probability(_: &Event) -> f64 {
|
||||
// real implementation would be more complex, of course
|
||||
0.95
|
||||
}
|
||||
|
||||
fn descriptive_probability(event: Event) -> &'static str {
|
||||
match probability(&event) {
|
||||
1.00 => "certain",
|
||||
0.00 => "impossible",
|
||||
0.00 ... 0.25 => "very unlikely",
|
||||
0.25 ... 0.50 => "unlikely",
|
||||
0.50 ... 0.75 => "likely",
|
||||
0.75 ... 1.00 => "very likely",
|
||||
_ => unreachable!()
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
std::io::println(descriptive_probability(NewRelease));
|
||||
}
|
||||
```
|
||||
|
||||
We shouldn't ever hit the `_` case, so we use the `unreachable!()` macro to
|
||||
indicate this. `unreachable!()` gives a different kind of error than `Result`.
|
||||
Rust calls these sorts of errors 'panics.'
|
||||
|
||||
# Handling errors with `Option` and `Result`
|
||||
|
||||
The simplest way to indicate that a function may fail is to use the `Option<T>`
|
||||
type. Remember our `from_str()` example? Here's its type signature:
|
||||
|
||||
```{rust,ignore}
|
||||
pub fn from_str<A: FromStr>(s: &str) -> Option<A>
|
||||
```
|
||||
|
||||
`from_str()` returns an `Option<A>`. If the conversion succeeds, it will return
|
||||
`Some(value)`, and if it fails, it will return `None`.
|
||||
|
||||
This is appropriate for the simplest of cases, but doesn't give us a lot of
|
||||
information in the failure case. What if we wanted to know _why_ the conversion
|
||||
failed? For this, we can use the `Result<T, E>` type. It looks like this:
|
||||
|
||||
```rust
|
||||
enum Result<T, E> {
|
||||
Ok(T),
|
||||
Err(E)
|
||||
}
|
||||
```
|
||||
|
||||
This enum is provided by Rust itself, so you don't need to define it to use it
|
||||
in your code. The `Ok(T)` variant represents a success, and the `Err(E)` variant
|
||||
represents a failure. Returning a `Result` instead of an `Option` is recommended
|
||||
for all but the most trivial of situations.
|
||||
|
||||
Here's an example of using `Result`:
|
||||
|
||||
```rust
|
||||
#[deriving(Show)]
|
||||
enum Version { Version1, Version2 }
|
||||
|
||||
#[deriving(Show)]
|
||||
enum ParseError { InvalidHeaderLength, InvalidVersion }
|
||||
|
||||
|
||||
fn parse_version(header: &[u8]) -> Result<Version, ParseError> {
|
||||
if header.len() < 1 {
|
||||
return Err(InvalidHeaderLength);
|
||||
}
|
||||
match header[0] {
|
||||
1 => Ok(Version1),
|
||||
2 => Ok(Version2),
|
||||
_ => Err(InvalidVersion)
|
||||
}
|
||||
}
|
||||
|
||||
let version = parse_version(&[1, 2, 3, 4]);
|
||||
match version {
|
||||
Ok(v) => {
|
||||
println!("working with version: {}", v);
|
||||
}
|
||||
Err(e) => {
|
||||
println!("error parsing header: {}", e);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This function makes use of an enum, `ParseError`, to enumerate the various
|
||||
errors that can occur.
|
||||
|
||||
# Non-recoverable errors with `panic!`
|
||||
|
||||
In the case of an error that is unexpected and not recoverable, the `panic!`
|
||||
macro will induce a panic. This will crash the current task, and give an error:
|
||||
|
||||
```{rust,ignore}
|
||||
panic!("boom");
|
||||
```
|
||||
|
||||
gives
|
||||
|
||||
```{notrust,ignore}
|
||||
task '<main>' panicked at 'boom', hello.rs:2
|
||||
```
|
||||
|
||||
when you run it.
|
||||
|
||||
Because these kinds of situations are relatively rare, use panics sparingly.
|
||||
|
||||
# Upgrading failures to panics
|
||||
|
||||
In certain circumstances, even though a function may fail, we may want to treat
|
||||
it as a panic instead. For example, `io::stdin().read_line()` returns an
|
||||
`IoResult<String>`, a form of `Result`, when there is an error reading the
|
||||
line. This allows us to handle and possibly recover from this sort of error.
|
||||
|
||||
If we don't want to handle this error, and would rather just abort the program,
|
||||
we can use the `unwrap()` method:
|
||||
|
||||
```{rust,ignore}
|
||||
io::stdin().read_line().unwrap();
|
||||
```
|
||||
|
||||
`unwrap()` will `panic!` if the `Option` is `None`. This basically says "Give
|
||||
me the value, and if something goes wrong, just crash." This is less reliable
|
||||
than matching the error and attempting to recover, but is also significantly
|
||||
shorter. Sometimes, just crashing is appropriate.
|
||||
|
||||
There's another way of doing this that's a bit nicer than `unwrap()`:
|
||||
|
||||
```{rust,ignore}
|
||||
let input = io::stdin().read_line()
|
||||
.ok()
|
||||
.expect("Failed to read line");
|
||||
```
|
||||
`ok()` converts the `IoResult` into an `Option`, and `expect()` does the same
|
||||
thing as `unwrap()`, but takes a message. This message is passed along to the
|
||||
underlying `panic!`, providing a better error message if the code errors.
|
||||
|
@ -227,52 +227,6 @@
|
||||
//! ```
|
||||
//!
|
||||
//! `try!` is imported by the prelude, and is available everywhere.
|
||||
//!
|
||||
//! # `Result` and `Option`
|
||||
//!
|
||||
//! The `Result` and [`Option`](../option/index.html) types are
|
||||
//! similar and complementary: they are often employed to indicate a
|
||||
//! lack of a return value; and they are trivially converted between
|
||||
//! each other, so `Result`s are often handled by first converting to
|
||||
//! `Option` with the [`ok`](type.Result.html#method.ok) and
|
||||
//! [`err`](type.Result.html#method.ok) methods.
|
||||
//!
|
||||
//! Whereas `Option` only indicates the lack of a value, `Result` is
|
||||
//! specifically for error reporting, and carries with it an error
|
||||
//! value. Sometimes `Option` is used for indicating errors, but this
|
||||
//! is only for simple cases and is generally discouraged. Even when
|
||||
//! there is no useful error value to return, prefer `Result<T, ()>`.
|
||||
//!
|
||||
//! Converting to an `Option` with `ok()` to handle an error:
|
||||
//!
|
||||
//! ```
|
||||
//! use std::io::Timer;
|
||||
//! let mut t = Timer::new().ok().expect("failed to create timer!");
|
||||
//! ```
|
||||
//!
|
||||
//! # `Result` vs. `panic!`
|
||||
//!
|
||||
//! `Result` is for recoverable errors; `panic!` is for unrecoverable
|
||||
//! errors. Callers should always be able to avoid panics if they
|
||||
//! take the proper precautions, for example, calling `is_some()`
|
||||
//! on an `Option` type before calling `unwrap`.
|
||||
//!
|
||||
//! The suitability of `panic!` as an error handling mechanism is
|
||||
//! limited by Rust's lack of any way to "catch" and resume execution
|
||||
//! from a thrown exception. Therefore using panics for error
|
||||
//! handling requires encapsulating code that may panic in a task.
|
||||
//! Calling the `panic!` macro, or invoking `panic!` indirectly should be
|
||||
//! avoided as an error reporting strategy. Panics is only for
|
||||
//! unrecoverable errors and a panicking task is typically the sign of
|
||||
//! a bug.
|
||||
//!
|
||||
//! A module that instead returns `Results` is alerting the caller
|
||||
//! that failure is possible, and providing precise control over how
|
||||
//! it is handled.
|
||||
//!
|
||||
//! Furthermore, panics may not be recoverable at all, depending on
|
||||
//! the context. The caller of `panic!` should assume that execution
|
||||
//! will not resume after the panic, that a panic is catastrophic.
|
||||
|
||||
#![stable]
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user