Module std::result [] [src]

Error handling with the Result type

Result<T, E> 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:

#[derive(Debug)]
enum Version { Version1, Version2 }

fn parse_version(header: &[u8]) -> Result<Version, &'static str> {
    match header.get(0) {
        None => Err("invalid header length"),
        Some(&1) => Ok(Version::Version1),
        Some(&2) => Ok(Version::Version2),
        Some(_) => 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 with it more succinct.

let good_result: Result<i32, i32> = Ok(10);
let bad_result: Result<i32, i32> = 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<i32, i32> = good_result.map(|i| i + 1);
let bad_result: Result<i32, i32> = bad_result.map(|i| i - 1);

// Use `and_then` to continue the computation.
let good_result: Result<bool, i32> = good_result.and_then(|i| Ok(i == 11));

// Use `or_else` to handle the error.
let bad_result: Result<i32, i32> = bad_result.or_else(|i| Ok(i + 20));

// Consume the result and return the contents with `unwrap`.
let final_awesome_result = good_result.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_all method defined for I/O types by the Write trait:

use std::io;

trait Write {
    fn write_all(&mut self, bytes: &[u8]) -> Result<(), io::Error>;
}

Note: The actual definition of Write uses io::Result, which is just a synonym for Result<T, io::Error>.

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:

use std::fs::File;
use std::io::prelude::*;

let mut file = File::create("valuable_data.txt").unwrap();
// If `write_all` errors, then we'll never know, because the return
// value is ignored.
file.write_all(b"important message");

If you do write that in Rust, the compiler will 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 assert success with expect. This will panic if the write fails, providing a marginally useful message indicating why:

use std::fs::File;
use std::io::prelude::*;

let mut file = File::create("valuable_data.txt").unwrap();
file.write_all(b"important message").expect("failed to write message");

You might also simply assert success:

assert!(file.write_all(b"important message").is_ok());

Or propagate the error up the call stack with try!:

fn write_message() -> io::Result<()> {
    let mut file = try!(File::create("valuable_data.txt"));
    try!(file.write_all(b"important message"));
    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:

use std::fs::File;
use std::io::prelude::*;
use std::io;

struct Info {
    name: String,
    age: i32,
    rating: i32,
}

fn write_info(info: &Info) -> io::Result<()> {
    let mut file = try!(File::create("my_best_friends.txt"));
    // Early return on error
    if let Err(e) = file.write_all(format!("name: {}\n", info.name).as_bytes()) {
        return Err(e)
    }
    if let Err(e) = file.write_all(format!("age: {}\n", info.age).as_bytes()) {
        return Err(e)
    }
    if let Err(e) = file.write_all(format!("rating: {}\n", info.rating).as_bytes()) {
        return Err(e)
    }
    Ok(())
}

With this:

use std::fs::File;
use std::io::prelude::*;
use std::io;

struct Info {
    name: String,
    age: i32,
    rating: i32,
}

fn write_info(info: &Info) -> io::Result<()> {
    let mut file = try!(File::create("my_best_friends.txt"));
    // Early return on error
    try!(file.write_all(format!("name: {}\n", info.name).as_bytes()));
    try!(file.write_all(format!("age: {}\n", info.age).as_bytes()));
    try!(file.write_all(format!("rating: {}\n", info.rating).as_bytes()));
    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, but it can only be used in functions that return Result because of the early return of Err that it provides.

Structs

IntoIter

An iterator over the value in a Ok variant of a Result.

Iter

An iterator over a reference to the Ok variant of a Result.

IterMut

An iterator over a mutable reference to the Ok variant of a Result.

Enums

Result

Result is a type that represents either success (Ok) or failure (Err).