Module core::result[src]
Error handling with the Result type
Result<T> is the type used for returning and propagating
errors. It is an enum with the variants, Ok(T), representing
success and containing a value, and Err(E), representing error
and containing an error value.
enum Result<T, E> { Ok(T), Err(E) }
Functions return Result whenever errors are expected and
recoverable. In the std crate Result is most prominently used
for I/O.
A simple function returning Result might be
defined and used like so:
#[deriving(Show)] enum Version { Version1, Version2 } fn parse_version(header: &[u8]) -> Result<Version, &'static str> { if header.len() < 1 { return Err("invalid header length"); } match header[0] { 1 => Ok(Version1), 2 => Ok(Version2), _ => Err("invalid version") } } let version = parse_version(&[1, 2, 3, 4]); match version { Ok(v) => { println!("working with version: {}", v); } Err(e) => { println!("error parsing header: {}", e); } }
Pattern matching on Results is clear and straightforward for
simple cases, but Result comes with some convenience methods
that make working it more succinct.
let good_result: Result<int, int> = Ok(10); let bad_result: Result<int, int> = Err(10); // The `is_ok` and `is_err` methods do what they say. assert!(good_result.is_ok() && !good_result.is_err()); assert!(bad_result.is_err() && !bad_result.is_ok()); // `map` consumes the `Result` and produces another. let good_result: Result<int, int> = good_result.map(|i| i + 1); let bad_result: Result<int, int> = bad_result.map(|i| i - 1); // Use `and_then` to continue the computation. let good_result: Result<bool, int> = good_result.and_then(|i| Ok(i == 11)); // Use `or_else` to handle the error. let bad_result: Result<int, int> = bad_result.or_else(|i| Ok(11)); // Consume the result and return the contents with `unwrap`. let final_awesome_result = good_result.ok().unwrap();
Results must be used
A common problem with using return values to indicate errors is
that it is easy to ignore the return value, thus failing to handle
the error. Result is annotated with the #[must_use] attribute,
which will cause the compiler to issue a warning when a Result
value is ignored. This makes Result especially useful with
functions that may encounter errors but don't otherwise return a
useful value.
Consider the write_line method defined for I/O types
by the Writer trait:
use std::io::IoError; trait Writer { fn write_line(&mut self, s: &str) -> Result<(), IoError>; }
Note: The actual definition of Writer uses IoResult, which
is just a synonym for Result<T, IoError>.
This method doesn't produce a value, but the write may fail. It's crucial to handle the error case, and not write something like this:
fn main() { use std::io::{File, Open, Write}; let mut file = File::open_mode(&Path::new("valuable_data.txt"), Open, Write); // If `write_line` errors, then we'll never know, because the return // value is ignored. file.write_line("important message"); drop(file); }use std::io::{File, Open, Write}; let mut file = File::open_mode(&Path::new("valuable_data.txt"), Open, Write); // If `write_line` errors, then we'll never know, because the return // value is ignored. file.write_line("important message"); drop(file);
If you do write that in Rust, the compiler will by give you a
warning (by default, controlled by the unused_must_use lint).
You might instead, if you don't want to handle the error, simply
fail, by converting to an Option with ok, then asserting
success with expect. This will fail if the write fails, proving
a marginally useful message indicating why:
use std::io::{File, Open, Write}; let mut file = File::open_mode(&Path::new("valuable_data.txt"), Open, Write); file.write_line("important message").ok().expect("failed to write message"); drop(file);
You might also simply assert success:
fn main() { use std::io::{File, Open, Write}; let mut file = File::open_mode(&Path::new("valuable_data.txt"), Open, Write); assert!(file.write_line("important message").is_ok()); drop(file); }assert!(file.write_line("important message").is_ok());
Or propagate the error up the call stack with try!:
fn write_message() -> Result<(), IoError> { let mut file = File::open_mode(&Path::new("valuable_data.txt"), Open, Write); try!(file.write_line("important message")); drop(file); return Ok(()); }
The try! macro
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:
fn main() { use std::io::{File, Open, Write, IoError}; struct Info { name: String, age: int, rating: int } fn write_info(info: &Info) -> Result<(), IoError> { let mut file = File::open_mode(&Path::new("my_best_friends.txt"), Open, Write); // Early return on error match file.write_line(format!("name: {}", info.name).as_slice()) { Ok(_) => (), Err(e) => return Err(e) } match file.write_line(format!("age: {}", info.age).as_slice()) { Ok(_) => (), Err(e) => return Err(e) } return file.write_line(format!("rating: {}", info.rating).as_slice()); } }use std::io::{File, Open, Write, IoError}; struct Info { name: String, age: int, rating: int } fn write_info(info: &Info) -> Result<(), IoError> { let mut file = File::open_mode(&Path::new("my_best_friends.txt"), Open, Write); // Early return on error match file.write_line(format!("name: {}", info.name).as_slice()) { Ok(_) => (), Err(e) => return Err(e) } match file.write_line(format!("age: {}", info.age).as_slice()) { Ok(_) => (), Err(e) => return Err(e) } return file.write_line(format!("rating: {}", info.rating).as_slice()); }
With this:
fn main() { use std::io::{File, Open, Write, IoError}; struct Info { name: String, age: int, rating: int } fn write_info(info: &Info) -> Result<(), IoError> { let mut file = File::open_mode(&Path::new("my_best_friends.txt"), Open, Write); // Early return on error try!(file.write_line(format!("name: {}", info.name).as_slice())); try!(file.write_line(format!("age: {}", info.age).as_slice())); try!(file.write_line(format!("rating: {}", info.rating).as_slice())); return Ok(()); } }use std::io::{File, Open, Write, IoError}; struct Info { name: String, age: int, rating: int } fn write_info(info: &Info) -> Result<(), IoError> { let mut file = File::open_mode(&Path::new("my_best_friends.txt"), Open, Write); // Early return on error try!(file.write_line(format!("name: {}", info.name).as_slice())); try!(file.write_line(format!("age: {}", info.age).as_slice())); try!(file.write_line(format!("rating: {}", info.rating).as_slice())); return Ok(()); }
It's much nicer!
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. Its simple definition
makes it clear:
macro_rules! try( ($e:expr) => (match $e { Ok(e) => e, Err(e) => return Err(e) }) )
try! is imported by the prelude, and is available everywhere.
Result and Option
The Result and Option 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 Results are often handled by first converting to
Option with the ok and
err 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. fail!
Result is for recoverable errors; fail! is for unrecoverable
errors. Callers should always be able to avoid failure if they
take the proper precautions, for example, calling is_some()
on an Option type before calling unwrap.
The suitability of fail! 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 failure for error
handling requires encapsulating fallible code in a task. Calling
the fail! macro, or invoking fail! indirectly should be
avoided as an error reporting strategy. Failure is only for
unrecoverable errors and a failing 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, failure may not be recoverable at all, depending on
the context. The caller of fail! should assume that execution
will not resume after failure, that failure is catastrophic.
Enums
| Result |
|
Functions
| collect | Takes each element in the |
| fold | Perform a fold operation over the result values from an iterator. |
| fold_ | Perform a trivial fold operation over the result values from an iterator. |