How to do error handling in Rust and what are the common pitfalls?

Rust generally solves errors in two ways:

  • Unrecoverable errors. Once you panic!, that’s it. Your program or thread aborts because it encounters something it can’t solve and its invariants have been violated. E.g. if you find invalid sequences in what should be a UTF-8 string.

  • Recoverable errors. Also called failures in some documentation. Instead of panicking, you emit a Option<T> or Result<T, E>. In these cases, you have a choice between a valid value Some(T)/Ok(T) respectively or an invalid value None/Error(E). Generally None serves as a null replacement, showing that the value is missing.


Now comes the hard part. Application.

Unwrap

Sometimes dealing with an Option is a pain in the neck, and you are almost guaranteed to get a value and not an error.

In those cases it’s perfectly fine to use unwrap. unwrap turns Some(e) and Ok(e) into e, otherwise it panics. Unwrap is a tool to turn your recoverable errors into unrecoverable.

if x.is_some() {
    y = x.unwrap(); // perfectly safe, you just checked x is Some
    // ...
}

Inside the if-block it’s perfectly fine to unwrap since it should never panic because we’ve already checked that it is Some with x.is_some().

If you’re writing a library, using unwrap is discouraged because when it panics the user cannot handle the error. Additionally, a future update may change the invariant. Imagine if the example above had if x.is_some() || always_return_true() . The invariant would changed, and unwrap could panic.

In this example, you should use ìf let instead:

if let Some(y) = x {
    // ...
}

? operator / try! macro

What’s the ? operator or the try! macro? A short explanation is that it either returns the value inside an Ok() or prematurely returns error.

Here is a simplified definition of what the operator or macro expand to:

macro_rules! try {
    ($e:expr) => (match $e {
        Ok(val) => val,
        Err(err) => return Err(err),
    });
}

If you use it like this:

let x = File::create("my_file.txt")?;
let x = try!(File::create("my_file.txt"));

It will convert it into this:

let x = match File::create("my_file.txt") {
    Ok(val)  => val,
    Err(err) => return Err(err),
};
 

The downside is that your functions now return Result.

Combinators

Option and Result have some convenience methods that allow chaining and dealing with errors in an understandable manner. Methods like and, and_then, or, or_else, ok_or, map_err, etc.

For example, you could have a default value in case your value is botched.

let x: Option<i32> = None;
let guaranteed_value = x.or(Some(3)); //it's Some(3)

Or if you want to turn your Option into a Result.

let x = Some("foo");
assert_eq!(x.ok_or("No value found"), Ok("foo"));

let x: Option<&str> = None;
assert_eq!(x.ok_or("No value found"), Err("No value found"));

This is just a brief skim of things you can do. For more explanation, check out:

Leave a Comment