I have a block of code where multiple optional variables need to be assigned at once. There is very little chance any of the values will be None
, so individually handing each failed case isn't especially useful.
Currently I write the checks like this:
if let Some(a) = foo_a() {
if let Some(b) = foo_b() {
if let Some(c) = foo_c() {
if let Some(d) = foo_d() {
// code
}
}
}
}
It would be convenient if it was possible to group assignments. Without this, adding a new variable indents the block one level, making for noisy diffs and causes unnecessarily deep indentation:
if let Some(a) = foo_a() &&
let Some(b) = foo_b() &&
let Some(c) = foo_c() &&
let Some(d) = foo_d()
{
// code
}
Is there a way to assign multiple Option
s in one if statement?
Some details worth noting:
The first function that fails should short circuit and not call the others. Otherwise, it could be written like this:
if let (Some(a), Some(b), Some(c), Some(d)) = (foo_a(), foo_b(), foo_c(), foo_d()) {
// Code
}
Deep indentation could be avoided using a function, but I would prefer not to do this since you may not want to have the body in a different scope...
fn my_function(a: Foo, b: Foo, c: Foo, d: Foo) {
// code
}
if let Some(a) = foo_a() {
if let Some(b) = foo_b() {
if let Some(c) = foo_c() {
if let Some(d) = foo_d() {
my_function(a, b, c, d);
}
}
}
}
As @SplittyDev said, you can create a macro to get the functionality you want. Here is an alternate macro-based solution which also retains the short-circuiting behaviour:
macro_rules! iflet {
([$p:pat = $e:expr] $($rest:tt)*) => {
if let $p = $e {
iflet!($($rest)*);
}
};
($b:block) => {
$b
};
}
fn main() {
iflet!([Some(a) = foo_a()] [Some(b) = foo_b()] [Some(c) = foo_c()] {
println!("{} {} {}", a, b, c);
});
}
Playground
The standard library doesn't include that exact functionality, but the language allows you to create the desired behavior using a small macro.
Here's what I came up with:
macro_rules! all_or_nothing {
($($opt:expr),*) => {{
if false $(|| $opt.is_none())* {
None
} else {
Some(($($opt.unwrap(),)*))
}
}};
}
You can feed it all your options and get some tuple containing the unwrapped values if all values are Some
, or None
in the case that any of the options are None
.
The following is a brief example on how to use it:
fn main() {
let foo = Some(0);
let bar = Some(1);
let baz = Some(2);
if let Some((a, b, c)) = all_or_nothing!(foo, bar, baz) {
println!("foo: {}; bar: {}; baz: {}", a, b, c);
} else {
panic!("Something was `None`!");
}
}
Here's a full test-suite for the macro: Rust Playground
My first inclination was to do something similar to swizard's answer, but to wrap it up in a trait to make the chaining cleaner. It's also a bit simpler without the need for extra function invocations.
It does have the downside of increasing the nesting of the tuples.
fn foo_a() -> Option<u8> {
println!("foo_a() invoked");
Some(1)
}
fn foo_b() -> Option<u8> {
println!("foo_b() invoked");
None
}
fn foo_c() -> Option<u8> {
println!("foo_c() invoked");
Some(3)
}
trait Thing<T> {
fn thing<F, U>(self, f: F) -> Option<(T, U)> where F: FnOnce() -> Option<U>;
}
impl<T> Thing<T> for Option<T> {
fn thing<F, U>(self, f: F) -> Option<(T, U)>
where F: FnOnce() -> Option<U>
{
self.and_then(|a| f().map(|b| (a, b)))
}
}
fn main() {
let x = foo_a()
.thing(foo_b)
.thing(foo_c);
match x {
Some(((a, b), c)) => println!("matched: a = {}, b = {}, c = {}", a, b, c),
None => println!("nothing matched"),
}
}
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