BudiBadu Logo
Samplebadu

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.