19.5 Rc<T>: Reference Counting for Shared Ownership

Rust’s ownership model typically mandates a single owner for each piece of data. That works well unless you have data that logically needs multiple owners—for instance, if multiple graph edges reference the same node.

Rc<T> (reference-counted) allows multiple pointers to share ownership of a single heap allocation. The data remains alive as long as there’s at least one Rc<T> pointing to it.

19.5.1 Why Rc<T>?

  • Without Rc<T>, “cloning” a pointer would create independent copies of the data rather than shared references.
  • For large, immutable data or complex shared structures, copying can be expensive or semantically incorrect.
  • Rc<T> ensures there’s exactly one underlying allocation, managed via a reference count.

19.5.2 How It Works

  • Each Rc<T> increments a reference count upon cloning.
  • When an Rc<T> is dropped, the count decrements.
  • Once the count reaches zero, the data is freed.

Not Thread-Safe
Rc<T> is designed for single-threaded scenarios only. For concurrent code, use Arc<T> instead.

Immutability
Rc<T> only provides shared ownership, not shared mutability. If you need to mutate the data while it’s shared, combine Rc<T> with interior mutability tools like RefCell<T>.

Example:

use std::rc::Rc;

#[derive(Debug)]
struct Node {
    value: i32,
}

fn main() {
    let node = Rc::new(Node { value: 42 });
    let edge1 = Rc::clone(&node);
    let edge2 = Rc::clone(&node);

    println!("Node via edge1: {:?}", edge1);
    println!("Node via edge2: {:?}", edge2);
    println!("Reference count: {}", Rc::strong_count(&node));
}

19.5.3 Limitations and Trade-Offs

  • Runtime Cost: Updating the reference count is relatively fast but not free.
  • No Thread-Safety: Attempting to share an Rc<T> across multiple threads causes compile-time errors.
  • Requires Careful Design: Cycles can form if you hold Rc<T> references in a circular manner, leading to memory that never frees. In such cases, use Weak<T> to break cycles.