Rust by Example: Rc and Arc
Rust 1.75+
Enable multiple ownership of data with Reference Counting. This guide compares Rc (for single-threaded scenarios) and Arc (for multi-threaded scenarios), showing how to share data safely.
Code
use std::rc::Rc;
use std::sync::Arc;
use std::thread;
fn main() {
// --- Rc (Reference Counted) ---
// Single-threaded only!
let a = Rc::new(String::from("shared data"));
println!("Count after creating a: {}", Rc::strong_count(&a));
let b = Rc::clone(&a); // Increments count, doesn't deep copy
println!("Count after creating b: {}", Rc::strong_count(&a));
{
let c = Rc::clone(&a);
println!("Count after creating c: {}", Rc::strong_count(&a));
println!("c accesses: {}", c);
} // c goes out of scope, count decreases
println!("Count after c drops: {}", Rc::strong_count(&a));
// --- Arc (Atomic Reference Counted) ---
// Thread-safe!
let shared_data = Arc::new(String::from("Thread-safe data"));
let data_ref = Arc::clone(&shared_data);
let handle = thread::spawn(move || {
println!("Thread sees: {}", data_ref);
});
handle.join().unwrap();
}Explanation
Sometimes a single value needs to have multiple owners (e.g., nodes in a graph). Rust enables this via Reference Counting. Both Rc and Arc store the data and a Control Block (containing strong and weak reference counts) on the heap.
They differ in thread safety:
- Rc (Reference Counted): Uses non-atomic operations for counting. It is faster but not thread-safe. It cannot be sent between threads.
- Arc (Atomic Reference Counted): Uses atomic operations. It is thread-safe but incurs a small performance penalty due to CPU cache synchronization. Use this when sharing data across threads.
Code Breakdown
12
Rc::clone(&a). We prefer calling Rc::clone explicitly rather than a.clone(). This makes it visually obvious that we are doing a cheap reference count increment, not a potentially expensive deep copy of the data.26
Arc::new(...). We switch to Arc because we intend to pass a reference to another thread. If we tried to use Rc here, the compiler would error saying Rc cannot be sent safely between threads.29
move ||. The closure captures data_ref by value. Because data_ref is an Arc, this moves the pointer (and ownership of that specific reference count) into the thread, ensuring the data stays alive while the thread runs.
