join! waits for everything. Sometimes you want the opposite: whichever of several things happens first, and none of the rest.
tokio::select! polls every branch and completes as soon as one of them does.
tokio::select! {
value = fetch_from_cache() => use_it(value),
value = fetch_from_disk() => use_it(value),
}Each arm is pattern = future => expression. Every arm must produce the same type, exactly like a match.
When one branch wins, the others are dropped mid-flight. Whatever they had done so far is thrown away.
That makes select! in a loop a sharp tool. A future that is safe to drop half-finished and poll again from scratch is called cancel safe. Receiver::recv is: a message is either taken or still in the channel, never lost in between. Reading half a line from a socket into a local buffer is not.
If several branches are ready at once, select! picks between them at random, so never write code that depends on branch order.
A future created inside a loop body starts over on every pass. For a deadline that is exactly wrong: the timer resets after each message and never fires. Create it once, pin it, then poll the same one with &mut.
let deadline = sleep(limit);
tokio::pin!(deadline);
loop {
tokio::select! {
v = rx.recv() => { /* ... */ }
_ = &mut deadline => break,
}
}tokio::pin! pins the future in place on the stack, which is what lets you take &mut of it.
first_ofpub async fn first_of<A, B>(a: A, b: B) -> String
where
A: Future<Output = String>,
B: Future<Output = String>,Return whichever future produces a value first, and drop the other.
collect_until_deadlinepub async fn collect_until_deadline(
rx: &mut mpsc::Receiver<i32>,
limit: Duration,
) -> Vec<i32>Collect messages in arrival order and stop at whichever comes first: the channel closing, or limit elapsing since the call started.
A producer sending steadily forever must not keep the collection running past limit.
rx.recv() yields None when every sender is gone. That ends the loop too.select! arm must return the same type, so do the work in a block.first_of is one select! with two arms, each binding a value and returning it.collect_until_deadline, build the sleep and tokio::pin! it before the loop.Option the receive arm produces: push on Some, break on None.join! waits for everything. Sometimes you want the opposite: whichever of several things happens first, and none of the rest.
tokio::select! polls every branch and completes as soon as one of them does.
tokio::select! {
value = fetch_from_cache() => use_it(value),
value = fetch_from_disk() => use_it(value),
}Each arm is pattern = future => expression. Every arm must produce the same type, exactly like a match.
When one branch wins, the others are dropped mid-flight. Whatever they had done so far is thrown away.
That makes select! in a loop a sharp tool. A future that is safe to drop half-finished and poll again from scratch is called cancel safe. Receiver::recv is: a message is either taken or still in the channel, never lost in between. Reading half a line from a socket into a local buffer is not.
If several branches are ready at once, select! picks between them at random, so never write code that depends on branch order.
A future created inside a loop body starts over on every pass. For a deadline that is exactly wrong: the timer resets after each message and never fires. Create it once, pin it, then poll the same one with &mut.
let deadline = sleep(limit);
tokio::pin!(deadline);
loop {
tokio::select! {
v = rx.recv() => { /* ... */ }
_ = &mut deadline => break,
}
}tokio::pin! pins the future in place on the stack, which is what lets you take &mut of it.
first_ofpub async fn first_of<A, B>(a: A, b: B) -> String
where
A: Future<Output = String>,
B: Future<Output = String>,Return whichever future produces a value first, and drop the other.
collect_until_deadlinepub async fn collect_until_deadline(
rx: &mut mpsc::Receiver<i32>,
limit: Duration,
) -> Vec<i32>Collect messages in arrival order and stop at whichever comes first: the channel closing, or limit elapsing since the call started.
A producer sending steadily forever must not keep the collection running past limit.
rx.recv() yields None when every sender is gone. That ends the loop too.select! arm must return the same type, so do the work in a block.first_of is one select! with two arms, each binding a value and returning it.collect_until_deadline, build the sleep and tokio::pin! it before the loop.Option the receive arm produces: push on Some, break on None.