Deep-Dive System Documentation
Single-Threaded Shared Ownership
In standard Rust, a value has exactly one owner. However, there are scenarios (like graph structures, trees, or shared state) where multiple references need read-only access to a single heap allocation. Rc<T> (Reference Counted) solves this by keeping track of the number of clones referencing the same memory block.
How It Works Under the Hood
When you allocate a value inside Rc<T>, Rust places the value on the heap along with two counters:
- Strong Count: The number of active owners. When this falls to
0, the value is safely dropped. - Weak Count: The number of non-owning references (
Weak<T>), used to prevent cycle memory leaks.
use std::rc::Rc;
// Heap allocation occurs here
let parent = Rc::new(String::from("Shared Node"));
println!("Strong count: {}", Rc::strong_count(&parent)); // 1
{
// Clone increments the strong counter (no heap allocation copy occurs)
let child = Rc::clone(&parent);
println!("Strong count: {}", Rc::strong_count(&parent)); // 2
println!("Shared content: {}", child);
} // child falls out of scope -> decrements count
println!("Strong count: {}", Rc::strong_count(&parent)); // 1
Shared Mutability Bypass
Rc<T> is strictly read-only (immutable). If you need shared mutability in a single-threaded environment, you must combine Rc<T> with interior mutability containers like RefCell<T>:
use std::rc::Rc;
use std::cell::RefCell;
let shared_data = Rc::new(RefCell::new(5));
*shared_data.borrow_mut() += 10; // bypasses compiler borrow check safely!
Cycle Memory Leaks
If two Rc pointers reference each other directly or via a chain, the strong count never hits 0, creating a memory leak.
- Fix: Use
Rc::downgrade(&rc)to create aWeak<T>pointer, which increments the weak counter instead of the strong counter.
Thread Safety Limits
Rc<T> is not thread-safe because it increments/decrements its counters using fast, non-atomic CPU operations. If you attempt to send an Rc<T> to another thread, the compiler will refuse to build, flagging a violation of the Send and Sync traits. For multi-threaded sharing, use Arc<T>.
Useful Methods
Rc::new(value: T) -> Rc<T>
Constructs a new Rc<T> on the heap, wrapping the given value and initializing strong/weak counters.
use std::rc::Rc;
let five = Rc::new(5);
Rc::clone(this: &Rc<T>) -> Rc<T>
Increments the strong reference count of the Rc pointer. Returns a clone pointing to the same heap allocation. This is $O(1)$ and avoids copying the inner value.
use std::rc::Rc;
let val = Rc::new(10);
let shared = Rc::clone(&val); // Reference count is now 2
Rc::strong_count(this: &Rc<T>) -> usize
Returns the number of strong references pointing to this allocation.
use std::rc::Rc;
let data = Rc::new("content");
assert_eq!(Rc::strong_count(&data), 1);
let cloned = Rc::clone(&data);
assert_eq!(Rc::strong_count(&data), 2);
Quick Reference Guide
use std::rc::Rc;
let data = Rc::new(vec![1, 2]);
let shared_ref = Rc::clone(&data);
let strong_count = Rc::strong_count(&data);