Why macro select! needs to work with Unpin Futures?

As I followed along reading about Futures, I understand that pinning is needed so that borrowing in the async block is valid. But now this book tells me that select![1] needs to work with Unpin Futures, I'm confused...

[1] select! - Asynchronous Programming in Rust

Here is the code snippet.


use futures::{
    future::FutureExt, // for `.fuse()`
    pin_mut,
    select,
};

async fn task_one() { /* ... */ }
async fn task_two() { /* ... */ }

async fn race_tasks() {
    let t1 = task_one().fuse();
    let t2 = task_two().fuse();

    pin_mut!(t1, t2);

    select! {
        () = t1 => println!("task one completed first"),
        () = t2 => println!("task two completed first"),
    }
}

I’m not completely familiar with futures::select, but judging by its documentation, the reason why Unpin is required is that select! is supposed to be callable in a loop, and hence doesn’t take ownership of the future, if it’s passed in by a simple identifier / name of the local variable.

On the other hand, if an expression is used that constructs the future, then Unpin is not required.

In your case you want to race two tasks and cancel the slower one; there’s no calling select! in a loop, so moving the future would be fine. Thus, as far as I understand the documentation, if you write () = { t1 } => … (and similar for t2), i.e. a block expression that moves the respective future, then Unpin should no longer be required. (Perhaps this even works with a parenthesized expression, i.e. () = (t1) => ….)


Edit: Tried it out, apparently this works

async fn race_tasks() {
    let t1 = task_one().fuse();
    let t2 = task_two().fuse();

    select! {
        () = {t1} => println!("task one completed first"),
        () = {t2} => println!("task two completed first"),
    }
}

It works with parentheses, too, but in that case it warns about “unnecessary” parentheses, so braces work better.


Edit2: Another point… in your snippet you are pinning t1 and t2 to the stack. This is necessary if select was called in a loop (then my solution above doesn’t work anymore). In case that wasn’t clear: The fact that your code snippet works is exactly in line with the “the futures used in select must implement both the Unpin trait” requirement, because, for any future type Fut, the type Pin<&mut Fut> is a future, too, and furthermore one that implements Unpin.

Relevant Unpin impls
-> Unpin in std::marker - Rust
-> Pin in std::pin - Rust
Relevant Future impl
-> Future in std::future - Rust

2 Likes

Thanks for your help!

Here is my current reasoning --

  1. A Future needs to be pinned in order to allow borrows within the Future itself to get polled.
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output>;
  1. join!(fut) takes ownership of fut (I assume it handles pinning itself).
  2. select! needs to work with a mutable reference of future since it allows futures to be polled again in a loop. So requiring Unpin prevents user ever passing the raw Future to select!.

Pinning the future via pin_mut or Box::Pin yields a Pin<T> where T derefs to Future and confusingly (but correctly) value of type Pin<T> is Unpin.

Is that correct understanding?

Basically, a pinned reference to a future is also a future, but moving the pinned reference doesn't move the underlying future, so it's ok.

2 Likes

Thanks for confirming it.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.