#280 Jul 25, 2026

280. Mutex::get_mut — You Have &mut, Stop Paying for lock()

A Mutex guards against other threads — but during setup and teardown there are no other threads. If you hold &mut Mutex<T> or own it outright, the borrow checker already proves exclusive access, and you can skip the lock entirely.

lock() works everywhere, so it’s easy to write it everywhere:

1
2
3
4
use std::sync::Mutex;

let m = Mutex::new(vec![1, 2, 3]);
m.lock().unwrap().push(4); // runtime lock — for nobody

Nothing else can even see m yet, but you still pay for an atomic operation and drag in the whole poisoning story. With a mutable binding, get_mut hands you the data directly:

1
2
let mut m = Mutex::new(vec![1, 2, 3]);
m.get_mut().unwrap().push(4); // no lock taken

This can’t block and can’t deadlock: &mut Mutex<T> is the proof that no one else holds the lock, checked at compile time instead of runtime. The unwrap only covers poisoning — a previous panic while locked — never contention.

The teardown twin is into_inner, which consumes the Mutex and gives the data back. The classic spot: after your threads are done, don’t lock to read the result — unwrap the wrapper:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
use std::thread;

let data = [1, 2, 3, 4, 5, 6];
let total = Mutex::new(0);

thread::scope(|s| {
    for chunk in data.chunks(2) {
        s.spawn(|| {
            *total.lock().unwrap() += chunk.iter().sum::<i32>()
        });
    }
});

// scope over — all threads joined, `total` is ours again
let sum = total.into_inner().unwrap();
assert_eq!(sum, 21);

Inside the scope, threads share &Mutex and must lock. After the scope, ownership snaps back, into_inner moves the value out, and the Mutex is gone — no final lock(), no clone() of the contents.

The same pair exists on RwLock, and the pattern generalizes: Arc::into_inner unwraps an Arc once the last clone is dropped, so an Arc<Mutex<T>> can be fully peeled back to a plain T after the last worker exits.

Locks are for sharing. When the type system says you’re alone, take the &mut and go.

Both stable since Rust 1.6.

← Previous 279. thread::park — Block a Thread Without Dragging In a Mutex