Send Futures
A Send future (F: Future + Send) is a future whose state machine can be safely moved from one OS worker thread to another between .await suspension points. Multi-threaded work-stealing executors (such as tokio::spawn) require spawned futures to implement Send so any idle worker thread can pick up and poll a woken task.
When Is an async Block or async fn Future Send?
Because a compiler-generated async future is an enum whose variants hold the variables live across each .await point, Send is derived automatically for the Future based on what is held across .await points:
$$\text{Future}: \text{Send} \iff \text{Every variable live across any } \texttt{.await} \text{ point is } \text{Send}$$
Work-Stealing Executor Migration Across `.await`:
Worker Thread 1: Polls `task` up to `socket.read().await` (returns `Poll::Pending`)
|
| (Task sits idle in heap; Worker Thread 2 steals `task`!)
v
Worker Thread 2: Polls `task` after `socket.read().await`!
---> Every variable kept alive inside `task` across `.await` MUST be `Send`!
Classic Pitfall: Holding a !Send Value (Rc<T>, Ref<'_, T>, MutexGuard<'_, T>) Across .await (E0277)
If a local variable of a !Send type—such as Rc<T>, RefCell's Ref<'_, T>, or std::sync::MutexGuard<'_, T>—remains in scope across an .await point, the enclosing Future becomes !Send! Attempting to pass that future to a function requiring F: Future + Send fails at compile time:
// ❌ does not compile
use std::future::{Future, ready};
use std::rc::Rc;
fn require_send_future<F: Future + Send>(fut: F) -> F {
fut
}
async fn holds_rc_across_await() -> u32 {
let non_send_rc = Rc::new(42_u32);
// `non_send_rc` (`!Send`) is still in scope across `ready(()).await`!
ready(()).await;
*non_send_rc
}
fn main() {
let _ = require_send_future(holds_rc_across_await());
}
error: future cannot be sent between threads safely
--> src/main.rs:17:33
|
17 | let _ = require_send_future(holds_rc_across_await());
| ^^^^^^^^^^^^^^^^^^^^^^^ future returned by `holds_rc_across_await` is not `Send`
|
= help: within `impl Future<Output = u32>`, the trait `Send` is not implemented for `Rc<u32>`
note: future is not `Send` as this value is used across an await
--> src/main.rs:12:11
|
10 | let non_send_rc = Rc::new(42_u32);
| ----------- has type `Rc<u32>` which is not `Send`
11 | // `non_send_rc` (`!Send`) is still in scope across `ready(()).await`!
12 | ready(()).await;
| ^^^^^^ await occurs here, with `non_send_rc` maybe used later
Fixing !Send Futures by Scoping !Send Guards Before .await
If a !Send value (such as a std::sync::MutexGuard<'_, T> or Rc<T>) is created, read/mutated, and dropped inside a { ... } block before the .await point, it does not cross the .await suspension point, so it is not stored in the suspended variant of the state machine—and the enclosing Future remains Send!
use std::future::{Future, ready};
use std::sync::Mutex;
fn assert_send_future<F: Future + Send>(fut: F) -> F {
fut
}
async fn update_and_await(shared: &Mutex<u32>) -> u32 {
// Scope the `!Send` `MutexGuard` inside `{ ... }` so it is dropped
// BEFORE `ready(()).await`!
let snapshot = {
let mut guard = shared.lock().unwrap();
*guard += 10;
*guard
}; // <-- `guard` (`!Send`) is dropped here!
ready(()).await;
snapshot * 2
}
fn main() {
let state = Mutex::new(11);
// Compiles cleanly because `update_and_await(&state)` implements `Send`!
let _send_fut = assert_send_future(update_and_await(&state));
println!("verified Future implements Send");
}
verified Future implements Send