Theme
Q068. How does tokio::select! work, and what happens to losing branches?
Short Answer
tokio::select! polls several branch futures on the current task and runs the handler for the first matching branch that becomes ready. The other branch futures are dropped when the select! expression ends unless they are borrowed futures stored outside it. This is concurrent but not necessarily parallel. Losing operations must be cancellation-safe, especially when select! is repeated in a loop.
Deep Dive
select! first evaluates branch preconditions, creates enabled futures, and polls them. By default Tokio randomizes which branch is checked first to reduce starvation when several are ready. biased; uses source order and makes fairness the programmer's responsibility.
A branch pattern can reject a ready output, disabling that branch for the remainder of the current macro call. An else branch runs only when all branches are disabled; otherwise no matching branch is a panic.
The key design question is what dropping a losing future means. Reading one framed message may be cancellation-safe, while read_exact, queued lock acquisition, or a multi-step protocol may lose partial progress or queue position. Sometimes the future must be created outside the loop and pinned so it can resume rather than restart.
Internal Model
All branch futures live inside one combined future generated by the macro. One poll of the parent polls enabled branches until one completes or all are pending.
After a handler is selected, local losing futures are dropped, running destructors for their initialized state. No automatic rollback of external side effects occurs.
Example
rust
use std::time::Duration;
#[tokio::main]
async fn main() {
let outcome = tokio::select! {
_ = tokio::time::sleep(Duration::from_millis(1)) => "timer",
_ = std::future::pending::<()>() => "never",
};
assert_eq!(outcome, "timer");
}Common Mistakes
- Saying
select!waits for all branches. - Assuming the losing operation is rolled back automatically.
- Using
biased;without ensuring low-priority branches cannot starve.
Follow-ups
- Which Tokio channel receive operations are cancellation-safe?
- How can a future be retained across iterations of a
select!loop? - Why does randomized polling order have a CPU cost?
Related
- Q067. When should you use
join!,spawn, orselect!? - Q069. How are futures and tasks cancelled in Rust?
- Q070. What does cancellation safety mean?
References
- Tokio Documentation