I'm trying to use the select!
macro's pre-condition to only include a branch if an Option
is the Some
variant.
I observe behavour which surprises me: the unwrap is eagerly evaluated causing panic!
unless contained within async { }
.
That is, this works as expected:
async fn foo(x: u32) {}
let maybe: Option<u32> = None;
select! {
_ = async { foo(maybe.unwrap()).await }, if maybe.is_some() => (),
_ = async { some_other_branch().await } => (),
}
...whereas this causes the thread to panic!
:
async fn foo(x: u32) {}
let maybe: Option<u32> = None;
select! {
_ = foo(maybe.unwrap()), if maybe.is_some() => (),
_ = async { some_other_branch().await } => (),
}
What's the reason for the difference in behaviour here?
It is because in the panicking version, the expression foo(maybe.unwrap())
is eagerly evaluated (to a future), which will conditionally be awaited. But it is too late; simply constructing foo(maybe.unwrap())
, in order that it may be awaited, panics because maybe
is None
.
On the other hand, the future async { foo(maybe.unwrap()).await }
doesn't panic (yet) because its body is only evaluated once it's awaited. And it will never be awaited because the precondition if maybe.is_some()
is false.
This is roughly analogous to the following code:
fn f() {
panic!()
}
fn g() {}
let index = 1;
// panics
let result1 = [f(), g()][index];
// this is fine; f() is never actually called
let fns: [Box<dyn Fn()>; 2] = [Box::new(|| f()), Box::new(|| g())];
let result2 = fns[index]();