Arc<T>
std::sync::Arc<T> (Atomically Reference Counted) is a thread-safe reference-counted smart pointer that provides shared ownership of a heap-allocated value T across multiple threads. Like Rc<T>, it tracks the number of active owners and drops T when the last Arc<T> is dropped, but it uses hardware atomic operations (AtomicUsize) for its reference counters so clones can be safely moved across thread boundaries.
Memory Layout of Arc<T>
An Arc<T> on the stack is a single thin pointer (8 bytes on 64-bit targets, or 16 bytes for unsized Arc<[T]> / Arc<str> / Arc<dyn Trait>). The heap block (ArcInner<T>) stores two AtomicUsize counters (strong and weak) adjacent to the payload T:
Thread 1 Stack Heap (ArcInner<T>) Thread 2 Stack
+---------------+ +--------------------------+ +---------------+
| arc1: Arc<T> -+------------> | strong: AtomicUsize (2) | <---+- arc2: Arc<T> |
+---------------+ | weak: AtomicUsize (1) | +---------------+
| data: T |
+--------------------------+
Sharing Immutable Data Across Threads
When T implements Send and Sync, Arc<T> also implements Send and Sync. To share data with a spawned thread, clone the Arc<T> on the parent thread before moving the clone into the move || closure:
use std::sync::Arc;
use std::thread;
fn main() {
let lookup_table = Arc::new(vec![10, 20, 30, 40, 50]);
let mut handles = Vec::new();
for worker_id in 0..3 {
let table_clone = Arc::clone(&lookup_table);
handles.push(thread::spawn(move || {
let sum: i32 = table_clone.iter().sum();
println!("worker {} computed sum = {}", worker_id, sum);
}));
}
for handle in handles {
handle.join().unwrap();
}
println!("final strong_count: {}", Arc::strong_count(&lookup_table));
}
worker 0 computed sum = 150
worker 1 computed sum = 150
worker 2 computed sum = 150
final strong_count: 1
Comparing Rc<T> and Arc<T>
| Feature | std::rc::Rc<T> |
std::sync::Arc<T> |
|---|---|---|
| Counter representation | Cell<usize> (non-atomic) |
AtomicUsize (hardware atomic instructions) |
Thread safety (Send / Sync) |
Never Send or Sync |
Send + Sync when T: Send + Sync |
| Clone / drop cost | Ordinary CPU increment/decrement | Atomic fetch_add / fetch_sub + memory fence on final drop |
| Companion weak pointer | std::rc::Weak<T> |
std::sync::Weak<T> |
| Companion interior mutability | RefCell<T>, Cell<T> |
Mutex<T>, RwLock<T>, Atomic* |
Mutating Shared State with Arc<Mutex<T>>
Because Arc<T> only grants shared &T access, mutating a value across threads requires wrapping the inner value in a thread-safe synchronization type such as Mutex<T>, RwLock<T>, or an atomic primitive:
use std::sync::{Arc, Mutex};
use std::thread;
fn main() {
let log = Arc::new(Mutex::new(Vec::new()));
thread::scope(|s| {
for id in 0..3 {
let log_ref = Arc::clone(&log);
s.spawn(move || {
log_ref.lock().unwrap().push(id);
});
}
});
let mut entries = log.lock().unwrap().clone();
entries.sort();
println!("recorded entries: {:?}", entries);
}
recorded entries: [0, 1, 2]