Theme
Q064. What makes a future Send, and why does tokio::spawn require Send + 'static?
Short Answer
A future is Send when all state it may carry across .await can be moved safely between threads. Tokio's multi-thread spawn may resume a task on another worker, so the future and its output must be Send. The 'static bound means the spawned future cannot contain non-'static borrowed data; it does not mean the task lives forever. Use async move with owned values such as Arc, or use a LocalSet for intentionally non-Send tasks.
Deep Dive
Locals that are created and dropped entirely between await points do not become suspended task state and therefore need not be Send. A common failure is keeping Rc, RefCell borrow guards, or non-Send lock guards alive across .await.
'static is required because the caller can drop its local stack frame while the detached task continues. Owning a String, Arc<T>, or other self-contained value satisfies the bound even though that value will be dropped when the task finishes.
tokio::spawn also requires Output: Send + 'static because the result may cross threads through JoinHandle. Current-thread execution with spawn_local relaxes Send, but ownership and cancellation still need explicit design.
Internal Model
The compiler determines the generated future's auto traits from captured values and fields live in suspended states. The executor is free to move the task allocation or schedule its poll on another worker according to its API guarantees.
Bounds are checked at compile time; Tokio does not test task fields dynamically.
Example
rust
use std::sync::Arc;
#[tokio::main]
async fn main() {
let symbol = Arc::new(String::from("BTC-USD"));
let task_symbol = Arc::clone(&symbol);
let task = tokio::spawn(async move {
tokio::task::yield_now().await;
task_symbol.len()
});
assert_eq!(task.await.unwrap(), symbol.len());
}Common Mistakes
- Explaining
'staticas "allocated forever" or "must be a static variable." - Assuming
async moveautomatically makes every captured valueSend. - Holding a non-
Sendvalue across.awaitand trying to fix only the spawn call.
Follow-ups
- How can narrowing a local scope make a future
Send? - When should
LocalSetandspawn_localbe used? - Why must a spawned task's output also be
Send + 'static?
Related
- Q045. What do
SendandSyncguarantee? - Q062. What happens internally at an
.awaitpoint? - Q065. What is the difference between awaiting a future and spawning a task?
References
- Async Book
- Tokio Documentation
- Rust Reference