Reputation: 1069
I find myself doing something like the following a lot:
fn foo() -> Result<i32, String> {
let cur = match something_that_returns_an_option() {
Some(cur) => cur,
None => return Err("Some error"),
};
// use `cur`
1
}
If I need several variables, I'm left with this pattern over and over again, or nested if lets/matches.
I there a more ergonomic way to handle repeatedly extracting values from Options?
Upvotes: 11
Views: 6640
Reputation: 11766
In your example you want to not just continue, break or return a regular value, but return an error. For that particular case, the Aiden4's answer is the way to go. Or, if you are using anyhow, you should see this.
But I've been in situations where I want to unwrap or (in the case of None
) directly continue
, break
or return
a non-error value.
The let-else feature was stabilized in Rust 1.65. Here is an example of how it can be used:
let options = vec![Some(74), None, Some(9)];
for o in options {
let Some(v) = o else { continue };
println!("v = {v}");
}
Rust (still) doesn't provide a short and concise way to do exactly that.
Here is a "one-liner" which kinda does the trick, but is still a bit verbose:
let v = if let Some(d) = some_option_value { d } else { continue; };
If you want a shorter solution, here is something to consider...
You can write a macro like this:
macro_rules! unwrap_or {
($e:expr, $or_do_what:expr) => {
if let Some(d) = $e { d } else { $or_do_what }
};
}
That will allow you to write code like this:
let options = vec![Some(74), None, Some(9)];
for o in options {
let v = unwrap_or!(o, continue);
// ...
}
That's a trivial example, but I think the biggest benefit can come if you need to perform multiple checks, so that instead of writing something "idiomatic" like this:
for thing in things {
if let Some(a) = thing {
// ...
if let Some(b) = a.another_opt {
// ...
if let Some(c) = a.yet_another_opt {
// ...
}
}
}
}
, you can simplify the code by avoiding the nesting of multiple blocks like this:
for thing in things {
let a = unwrap_or!(thing, continue);
// ...
let b = unwrap_or!(a.another_opt, continue);
// ...
let c = unwrap_or!(a.yet_another_opt, continue);
// ...
}
Whether that's a good practice is subjective, of course.
Upvotes: 8
Reputation: 2664
You are looking for Option::ok_or
. It lets you map an Option
into a Result
with the provided error. Combined with the ?
operator you clean things up nicely:
fn foo() -> Result<i32, String> {
let cur = something_that_returns_an_option().ok_or("some error")?;
Ok(cur + 1)
}
Option::ok_or_else
might also be helpful, as it evaluates the error branch lazily.
Upvotes: 13