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
- tokio::sync::Mutex: an async lock whose guard is
Send, so a task may hold it across.await. The second task waits inlock().await, suspended, with its thread free for other work.
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