future cannot be sent between threads safely, explained

Read the explanation
What if

A spawned task can resume on another worker thread after any .await, so a std MutexGuard has to be dropped first.

Predict first

add_one keeps its std MutexGuard alive across a .await, and the future goes to tokio::spawn. What happens?

012345678main: spawn 2 tasksspawnt.awaitprint 2worker 1: task 1: lock, add, drop guardtask 1worker 2: task 2: lock, add, drop guardtask 2worker 2: task 1 resumes heretask 1worker 1: task 2 resumes heretask 2

Tap any block to see what that thread is doing.

  • Running
  • Waiting / parked
  • Holding lock
Step 1/5

main spawns two tasks onto Tokio's worker threads.

1async fn add_one(count: Arc<Mutex<i32>>) {2    {3        let mut n = count.lock().unwrap();4        *n += 1;5    } // the guard is dropped here, before the .await6    tokio::time::sleep(Duration::from_millis(10)).await; // the task may resume on another thread7}8 9#[tokio::main]10async fn main() {11    let count = Arc::new(Mutex::new(0));12    let tasks: Vec<_> = (0..2).map(|_| tokio::spawn(add_one(Arc::clone(&count)))).collect();13    for t in tasks {14        t.await.unwrap();15    }16    println!("{}", *count.lock().unwrap());17}

Explanation

What just happened

Each task locked the std::sync::Mutex, added 1, and dropped the guard at the end of its block, before the .await. While they slept, no thread held them, and task 1 woke up on a different worker thread. Our tests catch tasks doing exactly that.

That’s the reason for the error. A std MutexGuard has to be released on the thread that locked it, so it isn’t Send. Keep it alive across the .await and the task could carry it to another thread, so the compiler refuses: future cannot be sent between threads safely.

Try the what-if switch

Common questions

What does future cannot be sent between threads safely mean?

tokio::spawn needs a Send future, because the task may resume on another worker thread after any .await. Something your async function keeps alive across an .await isn't Send, often a std::sync::MutexGuard.

How do I fix it with a std Mutex?

Drop the guard before the .await: put the lock and the work in their own block { ... }, so the guard goes out of scope first. Then the future is Send again and it compiles.

When should I use tokio::sync::Mutex?

When the lock really must stay held across an .await. Its guard is Send, and a task waiting for it is suspended instead of blocking its thread.

Verified: this timeline matches a real program, checked in CI on Rust 1.99 with Tokio 1.54. How we verify. Unclear or wrong? Tell us