In this lesson, you will learn how to safely modify data shared across multiple threads using atomic types.
Previously, we learned that Rust’s borrow checker enforces strict rules: you can have either one mutable reference or many immutable references, but never both at the same time. This rule exists to prevent data races. However, when we work with multiple threads, we often encounter a scenario where we want multiple threads to "own" a piece of data and be able to change it without holding an exclusive reference.
The Role of Atomic Types
Atomic types (like std::sync::atomic::AtomicUsize) provide a special kind of interior mutability. Normally, if you have a shared reference (&T), you cannot change the underlying data. Atomics are designed specifically to bypass this limitation for simple types like integers or booleans.
Because the CPU and the hardware guarantee that an operation on an Atomic type happens in a single, indivisible step—even if multiple threads try to access it simultaneously—Rust allows you to mutate the value even when you only have an immutable reference to the atomic container.
A Concrete Example
Imagine a shared counter across two threads:
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
use std::thread;
fn main() {
// We wrap our atomic in an Arc to share ownership across threads
let counter = Arc::new(AtomicUsize::new(0));
let mut handles = vec![];
for _ in 0..2 {
let counter_ref = Arc::clone(&counter);
let handle = thread::spawn(move || {
// We have an immutable reference to the AtomicUsize,
// yet we can still modify it safely!
counter_ref.fetch_add(1, Ordering::SeqCst);
});
handles.push(handle);
}
for handle in handles { handle.join().unwrap(); }
println!("Result: {}", counter.load(Ordering::SeqCst));
}
In this code, counter_ref is shared, but we call fetch_add to change the value. Because the operation is atomic, the Rust compiler knows it is safe to perform this mutation without needing an &mut T reference. The "mutability" happens inside the atomic type, hidden from the borrow checker's usual exclusive-access requirements.
Key Takeaway: Atomic types provide a thread-safe way to mutate data through a shared reference by ensuring that updates happen in a single, indivisible hardware operation.