Smart Pointers

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]