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, useWeak<T>
to break cycles.