Error Handling
Rust Condition Handling
No exceptions (except panics which should not be caught)
Instead, two basic things a function can do when an error is encountered at runtime
Handle the error
Fail altogether via
panic!()
Return some kind of result indicating an error to the caller, who can then deal with it
Error Handling: Panic
panic!
is that thing that should never happen in a production programPanic is a true exception: will be "unwound" by default, can be caught (!) but shouldn't be
Why unwinding? Because drops do cleanup by default, so leave the world safe as possible before exiting
Error Handling: Option
Option
is Rust's alternative to special "sentinel" values, for example null pointersEnum defined more or less like this:
enum Option<T> { Some(T), None, } use Option::*;
Definitely read the API docs for
std::option::Option
carefullyThe
Option
type is sometimes used for returning certain kinds of errorStyle question here: best practice is probably to use
Option
for situations where nothing has really "failed" per se, but the semantics get mooted a lot
Error Handling: Result
Result
is simple enum type with two value-carrying options:Ok
andErr
enum Result<T, E> { Ok(T), Err(E), } use Result::*;
Definitely read the API docs for
std::result::Result
carefully"Normal" way to deal with errors in standard library and your own code
Err
normally contains a value implementingstd::error::Error
; there are many kinds of these in the standard library
Error Handling: Unwrap and Expect
Methods of
Option
andResult
..unwrap()
panics with a generic message on failure,.expect("x")
panics with the message"x"
on failurePanic on
None
orErr
, otherwise return the contents ofSome
orOk
let n = Some(7).unwrap(); // n is now 7 let n = None.unwrap(); // program panics
Note
.unwrap_or_else()
, which is useful for supplying a default value or something in a few situations
An Example: Pop On Empty Stack
Can use a
Vec
as a stack:let mut stack = Vec::new(); let v = stack.pop();
Could return random garbage. This is the C way
Could return a "default value", but what? And ugh
Could
panic!()
: pretty drastic. This is the Python wayCould return a
Result
with a custom error indicating popping an empty stack. Probably right if intent is forpop()
to be a "partial function"Could return an
Option
— whatVec
does. This effectively indicates that popping an empty stack is not to be regarded as an error, but you should handle it. Probably right if intent is forpop()
to be a "total function"
Handling Potential Errors: Pattern Matching
Easiest to understand, but most verbose: just deal with the enum directly
// Print a str backward. let mut s = "hello".to_string(); while let Some(c) = s.pop() { print!("{}", c); } println!();
(See also the
readline
example.)
Handling Potential Errors: ?
Rust has the
?
operator. This takes anOption
orResult
and unwraps if good, early-returns from the calling function with the error otherwisefn is_negative(s: &str) -> Result<bool, std::num::ParseIntError> { let n: i64 = s.parse()?; Ok(n < 0) } fn main() { assert!(is_negative("-100").unwrap()); assert!(is_negative("-x").is_err()); }
It is allowed to declare
main()
to return aResult
orOption
. Ifmain()
returns anErr
you will get an error message formatted some debuggy wayfn main() -> Result<(), std::num::ParseIntError> { println!("{}", is_negative("")?); Ok(()) }
Handling Potential Errors: Error Combinators
It is common to handle errors in a "functional" style using methods provided by
Result
andOption
. This is handy when you want to keep going after an errorlet scaled_min = (0..n).min().and_then(|s| s / m).or_else(0);
This usage can be worth it, but can also be a bit tricky. It will take some practice