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. 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.