In this lesson, you will learn how to enable shared ownership of data that can be safely accessed across multiple execution threads using Arc.
The Problem with Rc
In our previous lessons, we explored Rc<T>, which provides shared ownership by keeping a reference count of how many pointers exist to a specific heap allocation. However, Rc is not thread-safe. If you try to send an Rc pointer to another thread, the compiler will stop you. Why? Because the internal reference counter in Rc is not updated atomically; if two threads attempt to increment or decrement the count simultaneously, they could cause a race condition, leading to memory corruption.
Introducing Arc
To share data between threads, Rust provides Arc<T>, which stands for Atomic Reference Counting.
Arc works almost identically to Rc: it allocates memory on the heap and maintains a reference count. The critical difference is that Arc uses atomic operations to update that count. Atomic operations are CPU-level instructions that guarantee a "read-modify-write" sequence happens as a single, uninterrupted unit. Because these operations are thread-safe, multiple threads can safely increment and decrement the reference count without causing data races.
Consider this example where we share a string across threads:
use std::sync::Arc;
use std::thread;
fn main() {
let data = Arc::new("Shared string content".to_string());
for _ in 0..3 {
let thread_data = Arc::clone(&data);
thread::spawn(move || {
println!("Thread sees: {}", thread_data);
}).join().unwrap();
}
}
In this snippet, Arc::clone creates a new pointer to the same heap location. Because Arc uses atomic primitives, the compiler knows it is safe to move these pointers into different threads. Note that Arc only provides shared ownership; if you need to mutate the shared data from multiple threads, you will eventually need to combine Arc with interior mutability primitives (like Mutex), which we will cover in future lessons.
Key Takeaway: Arc<T> enables thread-safe shared ownership by replacing the non-atomic reference counter of Rc<T> with atomic operations that synchronize access across multiple threads.