Rust generally solves errors in two ways:
-
Unrecoverable errors. Once you
panic!
, that’s it. Your program or thread aborts because it encounters something it can’t solve and its invariants have been violated. E.g. if you find invalid sequences in what should be a UTF-8 string. -
Recoverable errors. Also called failures in some documentation. Instead of panicking, you emit a
Option<T>
orResult<T, E>
. In these cases, you have a choice between a valid valueSome(T)
/Ok(T)
respectively or an invalid valueNone
/Error(E)
. GenerallyNone
serves as anull
replacement, showing that the value is missing.
Now comes the hard part. Application.
Unwrap
Sometimes dealing with an Option
is a pain in the neck, and you are almost guaranteed to get a value and not an error.
In those cases it’s perfectly fine to use unwrap
. unwrap
turns Some(e)
and Ok(e)
into e
, otherwise it panics. Unwrap is a tool to turn your recoverable errors into unrecoverable.
if x.is_some() {
y = x.unwrap(); // perfectly safe, you just checked x is Some
// ...
}
Inside the if
-block it’s perfectly fine to unwrap since it should never panic because we’ve already checked that it is Some
with x.is_some()
.
If you’re writing a library, using unwrap
is discouraged because when it panics the user cannot handle the error. Additionally, a future update may change the invariant. Imagine if the example above had if x.is_some() || always_return_true()
. The invariant would changed, and unwrap
could panic.
In this example, you should use ìf let
instead:
if let Some(y) = x {
// ...
}
?
operator / try!
macro
What’s the ?
operator or the try!
macro? A short explanation is that it either returns the value inside an Ok()
or prematurely returns error.
Here is a simplified definition of what the operator or macro expand to:
macro_rules! try {
($e:expr) => (match $e {
Ok(val) => val,
Err(err) => return Err(err),
});
}
If you use it like this:
let x = File::create("my_file.txt")?;
let x = try!(File::create("my_file.txt"));
It will convert it into this:
let x = match File::create("my_file.txt") {
Ok(val) => val,
Err(err) => return Err(err),
};
The downside is that your functions now return Result
.
Combinators
Option
and Result
have some convenience methods that allow chaining and dealing with errors in an understandable manner. Methods like and
, and_then
, or
, or_else
, ok_or
, map_err
, etc.
For example, you could have a default value in case your value is botched.
let x: Option<i32> = None;
let guaranteed_value = x.or(Some(3)); //it's Some(3)
Or if you want to turn your Option
into a Result
.
let x = Some("foo");
assert_eq!(x.ok_or("No value found"), Ok("foo"));
let x: Option<&str> = None;
assert_eq!(x.ok_or("No value found"), Err("No value found"));
This is just a brief skim of things you can do. For more explanation, check out: