Explanation
What just happened
Arc::clone gave each thread its own handle to the same Mutex. Thread 1 called lock() and got a guard, n. While n was alive, thread 2’s lock() waited. When thread 1’s closure ended, n was dropped and the lock released. Thread 2 then saw 1 and wrote 2.
No unlock() call anywhere: the guard going out of scope is the unlock.
Try the what-if switch
- lock held during work:
slow_work()doesn’t need the counter, butnwas still alive, so the lock stayed held. The threads did their slow work one after the other.drop(n)right after*n += 1lets them work at the same time.
Why Rust makes you write this
Try sharing a plain let mut count = 0 with thread::spawn. It doesn’t compile: the thread might outlive count. With move, each thread changes its own copy and main still sees 0. The data race from other languages can’t be written here.
Watch out
Keep guards short-lived. If a thread panics while holding one, the mutex is poisoned.
Common questions
Why do I need Arc<Mutex<T>> to share data between threads?
Arc lets several threads own the same value, and Mutex lets one thread at a time change it. Without them, a closure that borrows a local counter doesn't compile (error E0373), and Rc doesn't either: it can't be sent between threads (E0277).
When is a Mutex unlocked in Rust?
When the guard that lock() returned is dropped, usually at the end of its scope. Call drop(guard) to release it earlier.
What is a poisoned mutex?
A thread panicked while holding the guard. After that, every lock() returns an error, so lock().unwrap() panics too.
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