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 12:30:00 CDT on 2nd June 2015.

About Matt Godbolt

Matt Godbolt is a C++ developer living in Chicago. He works for Hudson River Trading on super fun but secret things. He is one half of the Two's Complement podcast. Follow him on Mastodon or Bluesky.