---
title: "22.3 Shared state: Mutex and Arc"
description: "Sharing the same data across threads safely: Mutex for exclusive access via locking, and Arc for thread-safe shared ownership."
url: "https://learnrust.net/chapter-22/shared-state-mutex-and-arc/"
last_updated: "2026-06-13"
---

# 22.3 Shared state: Mutex and Arc

Channels (lesson [22.2](https://learnrust.net/chapter-22/message-passing-channels/)) move data so it's never shared. But some problems are inherently about *shared* data: a counter many threads increment, a cache they all read and update, one configuration they all consult. For these you need multiple threads touching the *same* memory, and that's where concurrency is genuinely hard. Rust makes it safe with two cooperating tools: a **`Mutex`** to control access, and an **`Arc`** to share ownership. Neither alone is enough; together they're the standard pattern.

## Mutex: one thread at a time

A **mutex** (short for "mutual exclusion") is a lock around a piece of data. The rule it enforces: to touch the data, a thread must first acquire the lock, and only one thread can hold the lock at a time. Everyone else waits. This guarantees that at any instant exactly one thread is accessing the data, no two threads ever read-and-write it at once, which is precisely what a data race is.

In Rust, `Mutex<T>` *wraps* the data it protects, you can't get at the value without going through the lock. Here it is single-threaded, just to see the API:

```rust
use std::sync::Mutex;

fn main() {
    let m = Mutex::new(5);

    {
        let mut num = m.lock().unwrap();
        *num = 6;
    }   // lock released here when `num` goes out of scope

    println!("{m:?}");
}
```

```
Mutex { data: 6, poisoned: false, .. }
```

`m.lock()` acquires the lock, blocking until it's available, and returns a guard (a `MutexGuard`) that you use like a mutable reference to the inner value: `*num = 6` changes the data through it. The `lock()` returns a `Result` (it fails if another thread panicked while holding the lock, "poisoning" it), so we `unwrap`. The crucial part is the release: when the guard `num` goes out of scope, the lock is automatically released by its `Drop` (chapter 21, RAII at work). You never manually unlock; scope does it, which means you can't forget to unlock, the single most common mutex bug in other languages.

Notice that `Mutex` gave us mutation (`*num = 6`) even though `m` isn't `mut`. That's *interior mutability* (lesson [21.6](https://learnrust.net/chapter-21/refcell-and-interior-mutability/)) again: `Mutex` is the thread-safe `RefCell`, enforcing the borrow rules with a runtime lock instead of a runtime counter.

## Sharing the mutex across threads: enter Arc

Now the real goal: many threads sharing one mutex. They all need to *own* the mutex (it has to outlive each of them), which is shared ownership, the job of `Rc` from chapter 21. But `Rc` is single-threaded only (its count isn't safe to update concurrently), so the compiler won't let it cross a thread boundary. Its thread-safe sibling is **`Arc<T>`**: "atomically reference counted." Same shared-ownership idea as `Rc`, but with a counter that's safe to update from multiple threads at once.

The canonical example, ten threads each incrementing a shared counter:

```rust
use std::sync::{Arc, Mutex};
use std::thread;

fn main() {
    let counter = Arc::new(Mutex::new(0));
    let mut handles = vec![];

    for _ in 0..10 {
        let counter = Arc::clone(&counter);
        let handle = thread::spawn(move || {
            let mut num = counter.lock().unwrap();
            *num += 1;
        });
        handles.push(handle);
    }

    for handle in handles {
        handle.join().unwrap();
    }

    println!("result: {}", *counter.lock().unwrap());
}
```

```
result: 10
```

Read the shape, because it's the pattern you'll reuse constantly. `Arc::new(Mutex::new(0))` wraps the counter in a mutex (for safe access) inside an `Arc` (for shared ownership). The loop spawns ten threads; each gets its own `Arc::clone` (a cheap new handle to the same counter, lesson [21.5](https://learnrust.net/chapter-21/rc/)) and `move`s it into the thread. Inside, each thread locks, increments, and the lock releases at the end of the closure. We collect the handles and `join` them all so `main` waits for every thread. The result is reliably 10: the mutex guarantees the ten increments don't trample each other.

> **Arc<Mutex<T>> is Rc<RefCell<T>> for threads**
>
> The pairing mirrors chapter 21 exactly. Single-threaded shared mutable state was `Rc<RefCell<T>>`: `Rc` for many owners, `RefCell` for runtime-checked mutation. Across threads it becomes `Arc<Mutex<T>>`: `Arc` for many owners (thread-safe counting), `Mutex` for one-at-a-time mutation (locking). Same two-axis decision, "how many owners?" and "how is mutation controlled?", with the thread-safe versions of each tool. If you understood `Rc<RefCell<T>>`, you already understand `Arc<Mutex<T>>`; only the enforcement mechanism changed from a counter and a borrow flag to an atomic counter and a lock.

## Why two types instead of one

It's reasonable to ask why Rust splits this into `Arc` and `Mutex` rather than one combined type. Because they solve *independent* problems. `Arc` answers "who owns this?", many threads, freed when the last releases it, but `Arc` alone gives only shared *read* access, just like `Rc`. `Mutex` answers "how do we mutate it safely?", lock, change, unlock, but `Mutex` alone is a single value with no way to share it among threads. You need both because the problem has both halves: shared ownership *and* safe mutation. Keeping them separate also means you only pay for what you use, shared immutable data needs just `Arc`, and a mutex owned by one thread needs no `Arc`.

> **Locks can deadlock**
>
> Rust prevents data races, but it does *not* prevent **deadlock**: two threads each holding a lock the other needs, both waiting forever. If thread A locks mutex 1 then waits for mutex 2, while thread B locks mutex 2 then waits for mutex 1, neither can proceed. This compiles fine, it's not a memory-safety violation, but the program hangs. The defenses are discipline, not the compiler: acquire multiple locks in a consistent order everywhere, hold locks for as short a time as possible, and prefer message passing when you can. Fearless concurrency means no data races, not no logic bugs.

## Quiz time

**Question #1**

What does a `Mutex` guarantee, and how is the lock released?

<details class="solution">
<summary>Show solution</summary>

A `Mutex` guarantees that only one thread at a time can access the data it wraps: a thread must call `.lock()` (blocking until the lock is free) to get a guard it uses like a mutable reference, and all other threads wait. The lock is released *automatically* when the guard goes out of scope (its `Drop` runs, chapter-21 RAII), so you can't forget to unlock. This one-at-a-time access is what prevents data races on the shared data.

</details>

**Question #2**

Why do you need `Arc` *and* `Mutex` together to share mutable data across threads? What does each provide?

<details class="solution">
<summary>Show solution</summary>

`Arc` provides thread-safe *shared ownership*, multiple threads can each own a handle to the same value (with an atomic reference count safe to update concurrently), and the value lives until the last handle drops. `Mutex` provides *safe mutation*, exclusive, one-at-a-time access via locking. Neither alone suffices: `Arc` alone gives only shared read access; a bare `Mutex` can't be shared among threads. `Arc<Mutex<T>>` combines shared ownership with safe mutation.

</details>

**Question #3**

Why is `Arc` used here instead of `Rc`?

<details class="solution">
<summary>Show solution</summary>

`Rc`'s reference count is not safe to update from multiple threads simultaneously (it's non-atomic), so the compiler forbids sending an `Rc` across a thread boundary. `Arc` ("atomically reference counted") uses an atomic counter that *is* safe for concurrent updates, so it can be shared among threads. `Arc` is the thread-safe version of `Rc`; you pay a small extra cost for the atomic operations, which is why `Rc` still exists for single-threaded code.

</details>

You can now share data across threads safely, but *what makes a type safe to share at all*? The compiler decides this automatically using two marker traits. The next lesson ([22.4](https://learnrust.net/chapter-22/send-and-sync/)) introduces `Send` and `Sync`, the quiet machinery that makes "fearless" a compile-time guarantee rather than a hope.

## Sitemap

See the full [sitemap](https://learnrust.net/sitemap.md) for all pages.
