Theme
Q067. When should you use join!, spawn, or select!?
Short Answer
Use join! when all futures should run concurrently and you need all results. Use select! when you need to react to whichever branch becomes ready first, often for timeout, shutdown, or racing operations. Use spawn when work needs an independent scheduling and ownership boundary. join! and select! poll branches concurrently in one task; they do not make CPU work parallel.
Deep Dive
The choice expresses lifecycle:
join!keeps all child futures structurally owned by the parent and completes after all do;select!completes one enabled branch and usually drops or reuses the losing futures according to surrounding control flow;spawncreates independently scheduled tasks that require explicit joining, cancellation, and error handling.
For fallible futures, Tokio also provides try_join!, which returns on the first error. select! requires every losing operation to be cancellation-safe if dropping and recreating it could lose progress.
Spawning may provide scheduling isolation and parallel execution on a multi-thread runtime, but it adds allocation, scheduler, and 'static/Send constraints. Do not use it solely to satisfy the borrow checker.
Internal Model
join! and select! store child futures in the current task and poll them from the parent's poll. spawn allocates task metadata and adds the task to runtime queues.
Tokio rotates polling order in join! by default for fairness. select! uses randomized branch-check order unless biased; is specified.
Example
rust
async fn value(number: u64) -> u64 {
tokio::task::yield_now().await;
number
}
#[tokio::main]
async fn main() {
let (left, right) = tokio::join!(value(20), value(22));
assert_eq!(left + right, 42);
}Common Mistakes
- Using
spawnwhen structuredjoin!is sufficient. - Saying
join!runs each branch on a separate thread. - Racing a cancellation-unsafe operation in
select!.
Follow-ups
- How does
try_join!handle already-started sibling futures? - When does spawning improve fairness or isolation?
- What does
biased;change inselect!?
Related
- Q065. What is the difference between awaiting a future and spawning a task?
- Q068. How does
tokio::select!work, and what happens to losing branches? - Q070. What does cancellation safety mean?
References
- Tokio Documentation
- Async Book