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