Error handling in Rust

Previously, I appealed for help with my little Rust path tracer. Thankfully I had some help from Phil Dawes and now at least I “get” error handling, or at least I think I do!

Rust made the brave decision to not support exceptions. Instead, a rich set of features are used to build more explicit error handling:

The only thing remotely exception-like is the last-resort “panic” that prints a diagnostic message and exits the current thread.

Functions that can fail should return a Result<T, E> which itself is an enum holding either a return value of type T (wrapped in Ok), or an error of type E (which is wrapped in Err). Callers can then either match on the success (or otherwise), call unwrap() on the Result to turn it into its T (or panic if it was an error), or call one of the other convenience methods on Result (like is_ok()).

The standard input/output routines all return Result<T, io::Error> so to open a file one might do one of the following:

// this will panic if the file can't be opened
let file = File::open(&name).unwrap();

// this lets us check if the file opened ok
let file = match File::open(&name) {
    // Error? Return from this function with the
    // error.
    Err(x) => return x,
    // All ok? Get the file 
    Ok(f) => f
};

The pattern of returning the error to the caller if it’s an error, or else unwrapping the return value and using it, is so common it is enshrined in a Rust macro, try!. The macro machinery expands try!(X) into basically the same code as the match case above.

One caveat: using try! only works if you’re in a function whose return value is a Result. This first caught me out when copy/pasting examples from Stack Overflow into my fn main() function…the compiler errors were rather confusing.

In my little path tracer’s case I was interested in returning an error from the image merger’s loader. In my first attempt I had rather punted on error handling, choosing instead to panic! on parse errors. I didn’t like this, and now I return an error of my own.

I was returning an io::Result<PartialImage> to propagate the io::Results that the try! on the file routines would return on error. When it came to returning my own errors I was stuck: I couldn’t work out how make my own io::Results.

The solution is to use my own error type, and then make it convertible from io::Result:

enum ImageError {
    IoError(io::Error),
    BadFileError(String)
}

impl From<io::Error> for ImageError {
    fn from(e: io::Error) -> ImageError {
        ImageError::IoError(e)
    }
}

Once you know the trick of wrapping other librarys’ error types in your own, it mostly makes sense.

In a future post I hope to work out a nicer way of parsing the RGB triples using ranges.

Filed under: Coding Rust
Posted at 18:30:00 BST on 2nd June 2015.

About Matt Godbolt

Matt Godbolt is a C++ developer living in Chicago. Follow him on Mastodon or Bluesky.