0Pricing
Learn Rust Coding · Lesson

Sharing State with Arc/Mutex

Safe shared data.

Sharing State with Arc/Mutex is a free Learn Rust Coding lesson on CoddyKit — lesson 2 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the Learn Rust Coding learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.

Why Shared State Is Hard

Sometimes message passing is not enough and multiple threads truly need to read and write the same data. Rust will not let you share a mutable value across threads without protection, because that would risk a data race.

The two tools you combine are:

  • Arc for shared ownership across threads.
  • Mutex for safe, exclusive mutation.

Rc Is Not Thread-Safe

Rc gives shared ownership but only on a single thread. Its reference count is not synchronized, so the compiler refuses to send it between threads. For multi-threaded sharing you need Arc (Atomically Reference Counted).

Arc behaves like Rc but updates its count with atomic operations, making clones safe across threads.

use std::sync::Arc;

fn main() {
    let data = Arc::new(vec![1, 2, 3]);
    let clone1 = Arc::clone(&data);
    println!("original: {:?}", data);
    println!("clone:    {:?}", clone1);
    println!("count:    {}", Arc::strong_count(&data));
}

Mutex Provides Exclusive Access

A Mutex wraps data and guarantees only one thread accesses it at a time. You call .lock() to get a MutexGuard, which dereferences to the inner value. The lock is released automatically when the guard goes out of scope.

use std::sync::Mutex;

fn main() {
    let m = Mutex::new(5);
    {
        let mut num = m.lock().unwrap();
        *num += 10;
    } // guard dropped here, lock released
    println!("value = {:?}", m.lock().unwrap());
}

Combining Arc and Mutex

To share mutable data across threads you wrap it as Arc>:

  • Arc lets many threads own a handle to the same data.
  • Mutex lets each thread mutate it safely, one at a time.

Clone the Arc for each thread before spawning.

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

fn main() {
    let counter = Arc::new(Mutex::new(0));
    let c = Arc::clone(&counter);
    let handle = thread::spawn(move || {
        let mut n = c.lock().unwrap();
        *n += 1;
    });
    handle.join().unwrap();
    println!("counter = {}", *counter.lock().unwrap());
}

A Shared Counter Across Many Threads

The classic example: ten threads each increment a shared counter. Every thread holds its own Arc clone and locks the Mutex to add one. After joining all threads the total is exactly 10, with no data race.

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 c = Arc::clone(&counter);
        handles.push(thread::spawn(move || {
            let mut num = c.lock().unwrap();
            *num += 1;
        }));
    }
    for h in handles {
        h.join().unwrap();
    }
    println!("Result: {}", *counter.lock().unwrap());
}

Lock Scope Matters

The MutexGuard holds the lock until it is dropped. Holding it across slow work blocks other threads. Keep critical sections short: lock, mutate, release. Wrapping the lock in a small block ensures it drops before any extra processing.

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

fn main() {
    let log = Arc::new(Mutex::new(Vec::new()));
    let mut handles = vec![];
    for i in 0..3 {
        let l = Arc::clone(&log);
        handles.push(thread::spawn(move || {
            {
                let mut v = l.lock().unwrap();
                v.push(i);
            } // released quickly
        }));
    }
    for h in handles { h.join().unwrap(); }
    let mut result = log.lock().unwrap().clone();
    result.sort();
    println!("{:?}", result);
}

Deadlocks: A Real Risk

A deadlock happens when two threads each hold a lock the other needs, and both wait forever. Rust prevents data races but not deadlocks. Avoid them by always locking multiple mutexes in the same order and keeping locks short.

Also avoid locking the same Mutex twice on one thread; the standard Mutex is not reentrant.

Poisoning When a Thread Panics

If a thread panics while holding a lock, the Mutex becomes poisoned. Later .lock() calls return Err so you know the data may be inconsistent. You can recover the inner guard via into_inner() on the error if you decide the data is still usable.

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

fn main() {
    let data = Arc::new(Mutex::new(0));
    let d = Arc::clone(&data);
    let _ = thread::spawn(move || {
        let mut g = d.lock().unwrap();
        *g = 7;
        panic!("boom"); // poisons the mutex
    }).join();
    match data.lock() {
        Ok(g) => println!("ok: {}", *g),
        Err(poisoned) => println!("recovered: {}", *poisoned.into_inner()),
    }
}

RwLock for Many Readers

When reads vastly outnumber writes, an RwLock can be faster than a Mutex. It allows many simultaneous readers or one writer. Use .read() for shared access and .write() for exclusive access.

use std::sync::{Arc, RwLock};
use std::thread;

fn main() {
    let config = Arc::new(RwLock::new(String::from("v1")));
    let reader = Arc::clone(&config);
    let r = thread::spawn(move || {
        let val = reader.read().unwrap();
        println!("read: {}", *val);
    });
    r.join().unwrap();
    {
        let mut w = config.write().unwrap();
        *w = String::from("v2");
    }
    println!("final: {}", *config.read().unwrap());
}

Atomics for Simple Counters

For a single integer counter, a full Mutex is overkill. Types like AtomicUsize offer lock-free updates via methods such as fetch_add. Wrap them in Arc to share across threads. Choose an Ordering; SeqCst is the simplest safe default.

use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::thread;

fn main() {
    let counter = Arc::new(AtomicUsize::new(0));
    let mut handles = vec![];
    for _ in 0..5 {
        let c = Arc::clone(&counter);
        handles.push(thread::spawn(move || {
            c.fetch_add(1, Ordering::SeqCst);
        }));
    }
    for h in handles { h.join().unwrap(); }
    println!("count = {}", counter.load(Ordering::SeqCst));
}

Choosing the Right Tool

Quick guidance for shared state:

  • Channel: transfer ownership, pipeline style.
  • Arc<Mutex>: shared mutable structure, mixed read/write.
  • Arc<RwLock>: read-heavy shared data.
  • Atomics: single primitive counters or flags.

Prefer the simplest tool that fits; reach for locks only when message passing does not model the problem well.

Quick Check

Test your understanding of shared state.

Recap

You learned how to share state safely across threads:

  • Arc enables thread-safe shared ownership; Rc does not.
  • Mutex gives exclusive mutation via a guard that auto-unlocks.
  • Arc<Mutex<T>> is the standard pattern for shared mutable data.
  • Keep lock scopes short; beware deadlocks and poisoning.
  • RwLock suits read-heavy data; atomics suit simple counters.

Frequently asked questions

Is the “Sharing State with Arc/Mutex” lesson free?

Yes — the full text of “Sharing State with Arc/Mutex” is free to read here on the web, and the Learn Rust Coding course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the Learn Rust Coding course, upgrade to CoddyKit PRO.

What will I learn in “Sharing State with Arc/Mutex”?

Safe shared data. You practise Learn Rust Coding with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.

Do I need any experience to start Learn Rust Coding?

No prior experience is required. Learn Rust Coding on CoddyKit is structured for beginners through advanced learners; this is — lesson 2 of 4, so you can start here or from the beginning and move at your own pace.

How long does the “Sharing State with Arc/Mutex” lesson take?

Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.

Can I write and run code in this Learn Rust Coding lesson?

Yes. Every Learn Rust Coding lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.

All lessons in this course

  1. mpsc Channels
  2. Sharing State with Arc/Mutex
  3. Scoped Threads
  4. Crossbeam Channels
← Back to Learn Rust Coding