Confused about fused futures for select_biased!

I'm confused about how to correctly use select_biased! in a loop. I have some code like this (embedded no_std, in case that matters):

pub async fn run(&self) {
    loop {
        let a_fut = make_a_fut().fuse();
        let b_fut = make_b_fut().fuse();
        futures::pin_mut!(a_fut, b_fut);

        futures::select_biased! {
            a = a_fut => {
                log::info!("got a: {}", a);
                // handle A
            }

            b = b_fut => {
                log::info!("got b: {}", b);
                // handle B
            }
        }
    }
}

(I think it's not crucial exactly what a_fut and b_fut are, but for the sake of argument let's say they're something like embassy_sync::channel::Receiver::receive().)

The documentation for select_biased! says:

Note, though, that fusing a future or stream directly in the call to select_biased! will not be enough to prevent it from being polled after completion if the select_biased! call is in a loop, so when select_biased!ing in a loop, users should take care to fuse() outside of the loop.

This is ambiguous to me.

I'm not fusing the a_fut and b_fut futures "directly in the call to select" (which i take to mean "in the future's arm of the select") so that makes me think i'm ok. But i am "select_biased!ing in a loop" so do i need to "fuse() outside of the loop" (like, in the top level scope of the run() function)?

Please hit me with the clue-bat...

1 Like

The problem case would be if

  • SomeFuture is created outside the loop,
  • borrowed as Pin<&mut SomeFuture> for use in the loop,
  • then fused as Fuse<Pin<&mut SomeFuture>> inside the loop.

In that case, the Fuse wrapper would be ineffective, since after each loop iteration the Fuse is dropped and a new one created, which would then not know the “has terminated” state of the future.

As long as you're calling .fuse() on the original future and not a temporary borrow of it, you’re doing what you’re supposed to do.

3 Likes