rollup merge of #18903: steveklabnik/error_handling_guide

Now that we've done `fail` -> `panic`, I feel bringing back the error handling guide is a good idea. We had one long ago, but it was removed when conditions were removed.

This doesn't cover the new FromError stuff, but I feel like it's already useful in this state, so I'm sending this PR now.
This commit is contained in:
Jakub Bukaj 2014-11-19 22:37:07 +01:00
commit cbdaf2ebc7
6 changed files with 231 additions and 47 deletions

1
configure vendored
View File

@ -1031,6 +1031,7 @@ do
make_dir $h/test/doc-guide-tasks
make_dir $h/test/doc-guide-plugin
make_dir $h/test/doc-guide-crates
make_dir $h/test/doc-guide-error-handling
make_dir $h/test/doc-rust
done

View File

@ -27,7 +27,7 @@
######################################################################
DOCS := index intro tutorial guide guide-ffi guide-macros guide-lifetimes \
guide-tasks guide-container guide-pointers guide-testing \
guide-plugin guide-crates complement-bugreport \
guide-plugin guide-crates complement-bugreport guide-error-handling \
complement-lang-faq complement-design-faq complement-project-faq \
rustdoc guide-unsafe guide-strings reference

View File

@ -0,0 +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.

View File

@ -59,6 +59,7 @@ a guide that can help you out:
* [References and Lifetimes](guide-lifetimes.html)
* [Crates and modules](guide-crates.html)
* [Tasks and Communication](guide-tasks.html)
* [Error Handling](guide-error-handling.html)
* [Foreign Function Interface](guide-ffi.html)
* [Writing Unsafe and Low-Level Code](guide-unsafe.html)
* [Macros](guide-macros.html)

View File

@ -20,6 +20,7 @@
[type: text] src/doc/guide-testing.md $lang:doc/l10n/$lang/guide-testing.md
[type: text] src/doc/guide-unsafe.md $lang:doc/l10n/$lang/guide-unsafe.md
[type: text] src/doc/guide-crates.md $lang:doc/l10n/$lang/guide-crates.md
[type: text] src/doc/guide-error-handling.md $lang:doc/l10n/$lang/guide-error-handling.md
[type: text] src/doc/guide.md $lang:doc/l10n/$lang/guide.md
[type: text] src/doc/index.md $lang:doc/l10n/$lang/index.md
[type: text] src/doc/intro.md $lang:doc/l10n/$lang/intro.md

View File

@ -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]