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.
Matt Godbolt is a C++ developer living in Chicago. Follow him on Mastodon or Bluesky.