2015-01-16 14:30:27 -06:00
|
|
|
% Error Handling
|
2014-11-12 10:33:28 -06:00
|
|
|
|
2015-01-10 08:47:52 -06:00
|
|
|
> The best-laid plans of mice and men
|
2014-11-12 12:43:06 -06:00
|
|
|
> Often go awry
|
2014-12-09 12:26:04 -06:00
|
|
|
>
|
2014-11-12 12:43:06 -06:00
|
|
|
> "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
|
2015-01-08 18:52:50 -06:00
|
|
|
panic. A *failure* is an error that can be recovered from in some way. A
|
|
|
|
*panic* is an error that cannot be recovered from.
|
2014-11-12 12:43:06 -06:00
|
|
|
|
2015-01-08 18:52:50 -06:00
|
|
|
What do we mean by "recover"? Well, in most cases, the possibility of an error
|
2014-11-12 12:43:06 -06:00
|
|
|
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
|
2015-01-08 18:52:50 -06:00
|
|
|
inputs. It's expected behavior. We call this kind of error a *failure*.
|
2014-11-12 12:43:06 -06:00
|
|
|
|
|
|
|
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
|
2015-01-08 18:52:50 -06:00
|
|
|
state. Another example is using the `unreachable!()` macro:
|
2014-11-12 12:43:06 -06:00
|
|
|
|
|
|
|
```{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) {
|
2015-01-04 11:31:02 -06:00
|
|
|
1.00 => "certain",
|
|
|
|
0.00 => "impossible",
|
2014-11-12 12:43:06 -06:00
|
|
|
0.00 ... 0.25 => "very unlikely",
|
|
|
|
0.25 ... 0.50 => "unlikely",
|
|
|
|
0.50 ... 0.75 => "likely",
|
2015-01-04 11:31:02 -06:00
|
|
|
0.75 ... 1.00 => "very likely",
|
2014-11-12 12:43:06 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn main() {
|
|
|
|
std::io::println(descriptive_probability(NewRelease));
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
This will give us an error:
|
|
|
|
|
2014-12-10 14:12:16 -06:00
|
|
|
```text
|
2014-11-12 12:43:06 -06:00
|
|
|
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
|
2014-11-19 16:06:53 -06:00
|
|
|
use Event::NewRelease;
|
|
|
|
|
2014-11-12 12:43:06 -06:00
|
|
|
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) {
|
2015-01-04 11:31:02 -06:00
|
|
|
1.00 => "certain",
|
|
|
|
0.00 => "impossible",
|
2014-11-12 12:43:06 -06:00
|
|
|
0.00 ... 0.25 => "very unlikely",
|
|
|
|
0.25 ... 0.50 => "unlikely",
|
|
|
|
0.50 ... 0.75 => "likely",
|
2015-01-04 11:31:02 -06:00
|
|
|
0.75 ... 1.00 => "very likely",
|
2014-11-12 12:43:06 -06:00
|
|
|
_ => unreachable!()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn main() {
|
2014-11-19 16:06:53 -06:00
|
|
|
println!("{}", descriptive_probability(NewRelease));
|
2014-11-12 12:43:06 -06:00
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
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`.
|
2015-01-08 18:52:50 -06:00
|
|
|
Rust calls these sorts of errors *panics*.
|
2014-11-12 12:43:06 -06:00
|
|
|
|
|
|
|
# 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
|
2015-01-28 07:34:18 -06:00
|
|
|
#[derive(Debug)]
|
2014-11-12 12:43:06 -06:00
|
|
|
enum Version { Version1, Version2 }
|
|
|
|
|
2015-01-28 07:34:18 -06:00
|
|
|
#[derive(Debug)]
|
2014-11-12 12:43:06 -06:00
|
|
|
enum ParseError { InvalidHeaderLength, InvalidVersion }
|
|
|
|
|
|
|
|
fn parse_version(header: &[u8]) -> Result<Version, ParseError> {
|
|
|
|
if header.len() < 1 {
|
2014-11-19 16:06:53 -06:00
|
|
|
return Err(ParseError::InvalidHeaderLength);
|
2014-11-12 12:43:06 -06:00
|
|
|
}
|
|
|
|
match header[0] {
|
2014-11-19 16:06:53 -06:00
|
|
|
1 => Ok(Version::Version1),
|
|
|
|
2 => Ok(Version::Version2),
|
|
|
|
_ => Err(ParseError::InvalidVersion)
|
2014-11-12 12:43:06 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
let version = parse_version(&[1, 2, 3, 4]);
|
|
|
|
match version {
|
|
|
|
Ok(v) => {
|
2015-01-06 18:16:35 -06:00
|
|
|
println!("working with version: {:?}", v);
|
2014-11-12 12:43:06 -06:00
|
|
|
}
|
|
|
|
Err(e) => {
|
2015-01-06 18:16:35 -06:00
|
|
|
println!("error parsing header: {:?}", e);
|
2014-11-12 12:43:06 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
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!`
|
2015-01-10 03:45:51 -06:00
|
|
|
macro will induce a panic. This will crash the current thread, and give an error:
|
2014-11-12 12:43:06 -06:00
|
|
|
|
|
|
|
```{rust,ignore}
|
|
|
|
panic!("boom");
|
|
|
|
```
|
|
|
|
|
|
|
|
gives
|
|
|
|
|
2014-12-10 14:12:16 -06:00
|
|
|
```text
|
2015-01-10 03:45:51 -06:00
|
|
|
thread '<main>' panicked at 'boom', hello.rs:2
|
2014-11-12 12:43:06 -06:00
|
|
|
```
|
|
|
|
|
|
|
|
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");
|
|
|
|
```
|
2015-02-17 13:05:12 -06:00
|
|
|
|
2014-11-12 12:43:06 -06:00
|
|
|
`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.
|
2015-02-17 13:05:12 -06:00
|
|
|
|
|
|
|
# Using `try!`
|
|
|
|
|
|
|
|
When writing code that calls many functions that return the `Result` type, the
|
|
|
|
error handling can be tedious. The `try!` macro hides some of the boilerplate
|
|
|
|
of propagating errors up the call stack.
|
|
|
|
|
|
|
|
It replaces this:
|
|
|
|
|
|
|
|
```rust
|
|
|
|
use std::fs::File;
|
|
|
|
use std::io;
|
|
|
|
use std::io::prelude::*;
|
|
|
|
|
|
|
|
struct Info {
|
|
|
|
name: String,
|
|
|
|
age: i32,
|
|
|
|
rating: i32,
|
|
|
|
}
|
|
|
|
|
|
|
|
fn write_info(info: &Info) -> io::Result<()> {
|
|
|
|
let mut file = File::open("my_best_friends.txt").unwrap();
|
|
|
|
|
|
|
|
if let Err(e) = writeln!(&mut file, "name: {}", info.name) {
|
|
|
|
return Err(e)
|
|
|
|
}
|
|
|
|
if let Err(e) = writeln!(&mut file, "age: {}", info.age) {
|
|
|
|
return Err(e)
|
|
|
|
}
|
|
|
|
if let Err(e) = writeln!(&mut file, "rating: {}", info.rating) {
|
|
|
|
return Err(e)
|
|
|
|
}
|
|
|
|
|
|
|
|
return Ok(());
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
With this:
|
|
|
|
|
|
|
|
```rust
|
|
|
|
use std::fs::File;
|
|
|
|
use std::io;
|
|
|
|
use std::io::prelude::*;
|
|
|
|
|
|
|
|
struct Info {
|
|
|
|
name: String,
|
|
|
|
age: i32,
|
|
|
|
rating: i32,
|
|
|
|
}
|
|
|
|
|
|
|
|
fn write_info(info: &Info) -> io::Result<()> {
|
|
|
|
let mut file = try!(File::open("my_best_friends.txt"));
|
|
|
|
|
|
|
|
try!(writeln!(&mut file, "name: {}", info.name));
|
|
|
|
try!(writeln!(&mut file, "age: {}", info.age));
|
|
|
|
try!(writeln!(&mut file, "rating: {}", info.rating));
|
|
|
|
|
|
|
|
return Ok(());
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
Wrapping an expression in `try!` will result in the unwrapped success (`Ok`)
|
|
|
|
value, unless the result is `Err`, in which case `Err` is returned early from
|
|
|
|
the enclosing function.
|
|
|
|
|
|
|
|
It's worth noting that you can only use `try!` from a function that returns a
|
|
|
|
`Result`, which means that you cannot use `try!` inside of `main()`, because
|
|
|
|
`main()` doesn't return anything.
|
|
|
|
|
|
|
|
`try!` makes use of [`FromError`](../std/error/#the-fromerror-trait) to determine
|
|
|
|
what to return in the error case.
|