TL;DR:
Mutex<T>(mutual exclusion lock) in Rust wraps a valueTand ensures only one thread can access it at a time. You access the inner value by calling.lock(), which blocks until the lock is available and returns aMutexGuard<T>. The guard releases the lock automatically when it goes out of scope, no manual unlock needed. Combined withArc<T>for shared ownership,Arc<Mutex<T>>is the standard pattern for shared mutable state across threads in Rust.
What Is Mutex<T> in Rust?
Mutex<T> wraps a value and enforces mutual exclusion, only one thread can hold the lock and access the data at a time, preventing data races.
use std::sync::Mutex;
fn main() {
let m = Mutex::new(5); // wraps the value 5
{
let mut val = m.lock().unwrap(); // acquire lock
*val += 1; // modify the value
// MutexGuard dropped here; lock released automatically
}
println!("Value: {:?}", m); // Mutex { data: 6 }
}Unlike languages where locks are separate from data, Rust's Mutex<T> bundles the lock and the data, you cannot access the inner T without going through .lock(). This makes it impossible to forget to lock before accessing shared data.
How Do You Share a Mutex Across Threads?
Mutex<T> alone cannot be shared across threads, it needs Arc<T> to give each thread its own reference to the same Mutex.
use std::sync::{Arc, Mutex};
use std::thread;
fn main() {
let shared = Arc::new(Mutex::new(Vec::<i32>::new()));
let mut handles = vec![];
for i in 0..5 {
let shared = Arc::clone(&shared);
let handle = thread::spawn(move || {
let mut vec = shared.lock().unwrap();
vec.push(i);
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
let result = shared.lock().unwrap();
println!("{:?}", *result); // [0, 1, 2, 3, 4] (order may vary)
}Each thread calls Arc::clone to get a pointer to the same Mutex. Only one thread at a time can hold the MutexGuard, others block at .lock().
What Is RwLock and When Should You Use It?
RwLock<T> allows many concurrent readers or one exclusive writer, better than Mutex for read-heavy workloads.
use std::sync::{Arc, RwLock};
use std::thread;
fn main() {
let data = Arc::new(RwLock::new(vec![1, 2, 3]));
// Multiple threads can read simultaneously
let mut handles = vec![];
for _ in 0..3 {
let data = Arc::clone(&data);
handles.push(thread::spawn(move || {
let read_guard = data.read().unwrap(); // shared read lock
println!("{:?}", *read_guard);
}));
}
// One thread writes exclusively
let data_write = Arc::clone(&data);
handles.push(thread::spawn(move || {
let mut write_guard = data_write.write().unwrap(); // exclusive write lock
write_guard.push(4);
}));
for h in handles { h.join().unwrap(); }
}Mutex<T> | RwLock<T> | |
|---|---|---|
| Concurrent readers | ❌ (one at a time) | ✅ |
| Concurrent writers | ❌ | ❌ |
| Complexity | Lower | Higher (writer starvation risk) |
| Use when | Writes frequent | Reads dominant |
How Does Rust Prevent Data Races?
Rust's type system enforces that Mutex is the only way to get mutable access to shared data, the compiler prevents all unsynchronized concurrent mutation.
use std::sync::Mutex;
use std::thread;
fn main() {
let data = Mutex::new(0);
// This CANNOT compile; `data` is not Send if captured by reference
// thread::spawn(|| { *data.lock().unwrap() += 1; });
// Must use Arc to share across threads
let data = std::sync::Arc::new(Mutex::new(0));
let data2 = std::sync::Arc::clone(&data);
thread::spawn(move || { *data2.lock().unwrap() += 1; });
}In C++, nothing stops you from writing to shared data without a lock, the bug silently corrupts memory. In Rust, attempting to share unsynchronized mutable data is a compile error.
What Is a Mutex Deadlock and How Do You Avoid It?
A deadlock occurs when two threads each hold one lock and wait for the other's lock, neither can proceed. Rust prevents data races but cannot prevent deadlocks.
use std::sync::{Arc, Mutex};
fn main() {
let lock_a = Arc::new(Mutex::new(0));
let lock_b = Arc::new(Mutex::new(0));
// Thread 1: acquires A, then tries B
// Thread 2: acquires B, then tries A
// → Deadlock if they run simultaneously
// Prevention: always acquire locks in the same order
// Or: use a single Mutex wrapping both values
let combined = Arc::new(Mutex::new((0, 0)));
}Also avoid holding a MutexGuard across an .await in async code, use tokio::sync::Mutex instead, which is designed to be held across await points.
Frequently Asked Questions
.lock() returns Result<MutexGuard, PoisonError>. A mutex is "poisoned" if a thread panicked while holding the lock, the PoisonError signals that the data may be in an inconsistent state. .unwrap() panics on a poisoned mutex. In production, handle it: m.lock().unwrap_or_else(|e| e.into_inner()).
Avoid std::sync::Mutex across .await points, it blocks the thread, which defeats async. Use tokio::sync::Mutex for async code. For non-async critical sections in async code, std::sync::Mutex is fine as long as you release the guard before the next .await.
If a thread panics while holding a Mutex, the mutex is marked "poisoned." Subsequent .lock() calls return Err(PoisonError). This signals that the protected data may be in a corrupt state. Recover with .into_inner() if you know the data is still valid.
The parking_lot crate provides Mutex and RwLock that are faster and smaller than std::sync equivalents. They also do not poison, .lock() always succeeds. Many Rust projects use parking_lot as a drop-in replacement.
Sources
- The Rust Book ; Using Mutexes to Allow Access to Data from One Thread at a Time
- std::sync::Mutex ; Rust Standard Library
Related Glossary Terms
- Arc:
Arc<Mutex<T>>is the standard shared mutable state pattern - Ownership:
Mutexmoves ownership;.lock()temporarily borrows the inner value - Async/Await: Use
tokio::sync::Mutexin async contexts - Trait:
Mutex<T>implementsSend + SyncwhenT: Send - RwLock: Prefer
RwLockwhen reads dominate and writes are rare - Interior Mutability:
Mutex<T>is the thread-safe form of Rust's interior mutability pattern
Keep Reading
- Rust Ownership and Borrowing Explained: Mutex wraps ownership to enforce exclusive access
- Rust Memory Safety: NSA and CISA: data race freedom via Mutex is a key Rust guarantee
- Rust in the Linux Kernel: Mutex in kernel driver code
