In this lesson, you will learn how to manage globally accessible application state across concurrent threads using atomic smart pointers.
The Challenge of Global State
In service architectures—such as web servers or concurrent task runners—multiple threads often need to read and update shared data simultaneously. Because Rust enforces strict ownership rules, you cannot simply pass a mutable reference to a global variable across thread boundaries. Instead, we use a combination of Arc (Atomic Reference Counting) and Mutex (or RwLock) to ensure memory safety while providing shared access.
Orchestrating Shared State
When you need to share state, Arc provides the "shared" aspect: it allows multiple owners to keep the data alive as long as at least one reference exists. However, Arc only grants immutable access. To modify that data safely, you wrap your state inside a Mutex or RwLock.
Consider a simple shared counter for a service:
use std::sync::{Arc, Mutex};
use std::thread;
fn main() {
// Wrap the data in Mutex for interior mutability,
// then Arc to share it across threads.
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!("Final count: {}", *counter.lock().unwrap());
}
Why This Pattern Works
The Arc pointer handles the reference counting in an atomic way, ensuring that the memory isn't dropped prematurely as threads finish their tasks. The Mutex provides the "interior mutability" required to transition from the shared, immutable Arc reference to a mutable state. By calling .lock(), we obtain a guard that grants us temporary, safe access to the underlying data, adhering to Rust’s core guarantee that data is either shared or mutated, but never both at the same time in an uncontrolled fashion.
Key Takeaway: Use Arc<Mutex<T>> to distribute mutable ownership of service state across threads, relying on atomic counting for memory management and mutexes for synchronous interior mutability.