Using match (like in bar
) seems to be a common approach..
#[derive(Debug)]
pub enum MyErrors {
SomeError,
}
fn foo(x: Option<u64>) -> Result<u64, MyErrors> {
if x.is_none() {
return Err(MyErrors::SomeError);
}
// .. some long code where more options
// are checked and matched
// The ok here is just so the code is simple and compiles
Ok(x.unwrap() * 2)
}
fn bar(x: Option<u64>) -> Result<u64, MyErrors> {
match x {
None => {
return Err(MyErrors::SomeError)?;
}
Some(v) => {
// .. some long code where more options
// are checked and matched
// The ok here is just so the code is simple and compiles
Ok(x.unwrap() * 2)
}
}
}
fn main() {
foo(Some(1));
bar(Some(2));
}
However, early returns (such as in foo
) significantly reduce how nested the code looks like. If there are multiple times when an option has to be unwrapped or an error returned, code like bar gets very nested...
What is the recommended practice for early returning an error in the case of empty options?
Option types are very common in Rust code, as they have a number of uses: Initial values. Return values for functions that are not defined over their entire input range (partial functions) Return value for otherwise reporting simple errors, where None is returned on error. Optional struct fields.
To check if an Option is None you can either use Option::is_none or use the if let syntax. or if x == None .
The Option<T> enum in Rust can cater to two variants: None : represents a lack of value or if an error is encountered. Some(value) : value with type T wrapped in a tuple.
If a longer method chain is undesirable due to complex logic inside, there are still a few readable, low-indent options.
ok_or
and ?
We can convert an Option
to a Result
with a desired error, and immediately unwrap it with the ?
operator. This solution probably provides the least indent possible, and can be easily used to "unwrap" multiple Option
s.
fn bar1(x: Option<u64>) -> Result<u64, MyErrors> {
let x = x.ok_or(MyErrors::SomeError)?;
// A lot of stuff going on.
Ok(x * 2)
}
This will evaluate the error inside ok_or
regardless of whether or not it will actually be used. If this computation is expensive, ok_or_else
, which produces the error lazily, will be more efficient (related question).
if let
This solution can still lead to a staircase of code if nested, but may be more appropriate if the else
branch logic is more involved.
fn bar2(x: Option<u64>) -> Result<u64, MyErrors> {
if let Some(x) = x {
// Lot of stuff here as well.
Ok(x * 2)
} else {
Err(MyErrors::SomeError)
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With