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::Result
s 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::Result
s.
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.