Rc<T> — Reference Counting
Rust's ownership model normally allows exactly one owner per value. That is usually the right default, but sometimes multiple parts of your program need to read the same data, and none of them is a clear "primary" owner.
Rc<T> (Reference Counted) solves this problem. It keeps a count of how many owners
exist and only frees the underlying data when that count reaches zero.
The Problem Rc Solves
Imagine a graph where several nodes all reference the same shared configuration. With plain ownership you would have to pick one owner, which forces every other node to borrow — and borrow lifetimes must be tracked at compile time. When the relationship is dynamic (built at runtime), that becomes unwieldy.
Rc<T> lets multiple owners exist simultaneously without complex lifetime
annotations. The trade-off is that it only works on a single thread.
Creating and Cloning an Rc
Create an Rc<T> with Rc::new(). To create an additional owner, call
Rc::clone(&rc). This does not clone the underlying data — it only increments
the reference count, which is a very cheap operation.
use std::rc::Rc;
fn main() {
let a = Rc::new(String::from("hello"));
println!("count after creating a: {}", Rc::strong_count(&a)); // 1
let b = Rc::clone(&a);
println!("count after cloning to b: {}", Rc::strong_count(&a)); // 2
{
let c = Rc::clone(&a);
println!("count after cloning to c: {}", Rc::strong_count(&a)); // 3
} // c is dropped here
println!("count after c is dropped: {}", Rc::strong_count(&a)); // 2
// Both a and b point to the same String
println!("a = {}", a);
println!("b = {}", b);
} // b dropped -> count 1, then a dropped -> count 0 -> String is freedcount after creating a: 1 count after cloning to b: 2 count after cloning to c: 3 count after c is dropped: 2 a = hello b = hello
Rc::clone(&rc) rather than rc.clone(). Both work, but the explicit form makes it clear to readers that you are incrementing a reference count, not performing an expensive deep clone of the underlying data.Rc Gives Only Immutable Access
Rc<T> does not allow mutation through the shared reference. If multiple owners
could mutate the same data simultaneously, you would have data races even on a single
thread, since mutation could invalidate other owners' views.
use std::rc::Rc;
fn main() {
let a = Rc::new(vec![1, 2, 3]);
let b = Rc::clone(&a);
// Reading through either handle is fine
println!("a: {:?}", a);
println!("b: {:?}", b);
// Mutation is not allowed directly through Rc:
// a.push(4); // ERROR: cannot borrow data in an Rc as mutable
}Rc with RefCell: use Rc<RefCell<T>>. This pattern is covered on the RefCell page.Rc is NOT Thread-Safe
Rc<T> uses a plain (non-atomic) integer for its reference count. Incrementing and
decrementing that integer from multiple threads simultaneously is a data race.
The compiler prevents you from sending an Rc to another thread.
For multi-threaded shared ownership, use Arc<T> (Atomically Reference Counted)
instead — it has the same API but uses atomic operations for the count.
Type | Thread-safe | Count update | Use when |
|---|---|---|---|
Rc<T> | No | Plain integer (fast) | Single-threaded shared ownership |
Arc<T> | Yes | Atomic (slightly slower) | Multi-threaded shared ownership |
Reference Cycles and Memory Leaks
Rc<T> frees data when the reference count reaches zero. But if two values hold
Rc pointers to each other, their counts never reach zero — even after all external
references are gone. This is a reference cycle and it leaks memory.
use std::rc::Rc;
use std::cell::RefCell;
#[derive(Debug)]
struct Node {
value: i32,
next: Option<Rc<RefCell<Node>>>,
}
fn main() {
let a = Rc::new(RefCell::new(Node { value: 1, next: None }));
let b = Rc::new(RefCell::new(Node { value: 2, next: None }));
// a points to b
a.borrow_mut().next = Some(Rc::clone(&b));
// b points back to a — creating a cycle
b.borrow_mut().next = Some(Rc::clone(&a));
println!("a strong count: {}", Rc::strong_count(&a)); // 2
println!("b strong count: {}", Rc::strong_count(&b)); // 2
// When a and b go out of scope, counts drop to 1 (not 0) — memory leak!
}Rc cause memory leaks that the Rust compiler cannot detect. Always review code where Rc values can point back to each other. The standard fix is to break the cycle with Weak<T>.Weak<T> — Breaking Reference Cycles
Weak<T> is a non-owning reference. It does not increment the strong count, so
it does not prevent the value from being dropped. To access the value through a
Weak<T> you call .upgrade(), which returns Option<Rc<T>> — None if the
value has already been freed.
Use Rc::downgrade(&rc) to create a Weak<T> from an Rc<T>.
use std::rc::{Rc, Weak};
use std::cell::RefCell;
#[derive(Debug)]
struct Node {
value: i32,
// Weak reference — does not keep 'parent' alive
parent: Option<Weak<RefCell<Node>>>,
children: Vec<Rc<RefCell<Node>>>,
}
fn main() {
let parent = Rc::new(RefCell::new(Node {
value: 1,
parent: None,
children: vec![],
}));
let child = Rc::new(RefCell::new(Node {
value: 2,
parent: Some(Rc::downgrade(&parent)), // weak — no cycle
children: vec![],
}));
parent.borrow_mut().children.push(Rc::clone(&child));
println!("parent strong count: {}", Rc::strong_count(&parent)); // 1
println!("child strong count: {}", Rc::strong_count(&child)); // 2
// Access parent through weak reference
if let Some(p) = child.borrow().parent.as_ref().and_then(|w| w.upgrade()) {
println!("child's parent value: {}", p.borrow().value);
}
} // Both nodes are freed correctly — no cycleparent strong count: 1 child strong count: 2 child's parent value: 1
Rc::strong_count and Rc::weak_count
use std::rc::Rc;
fn main() {
let value = Rc::new(42);
let strong1 = Rc::clone(&value);
let weak1 = Rc::downgrade(&value);
let weak2 = Rc::downgrade(&value);
println!("strong: {}", Rc::strong_count(&value)); // 2
println!("weak: {}", Rc::weak_count(&value)); // 2
drop(strong1);
println!("strong after drop: {}", Rc::strong_count(&value)); // 1
// Weak references can still try to upgrade while strong count > 0
match weak1.upgrade() {
Some(v) => println!("upgraded: {}", v),
None => println!("value was dropped"),
}
drop(value); // last strong owner dropped
// Now upgrade returns None
match weak2.upgrade() {
Some(v) => println!("upgraded: {}", v),
None => println!("value was dropped"),
}
}strong: 2 weak: 2 strong after drop: 1 upgraded: 42 value was dropped
Real-World Use Cases for Rc
Shared configuration — multiple subsystems read the same parsed config without copying it.
Abstract Syntax Tree nodes — parent and sibling nodes share references during tree traversal.
Graph data structures — vertices hold shared references to edges and neighbouring vertices.
Observer pattern — a subject holds weak references to observers; observers hold strong Rc to shared state.
Summary: Rc<T> at a Glance
use std::rc::{Rc, Weak};
fn main() {
// Create
let owner = Rc::new(String::from("shared data"));
// Clone — cheap, increments count
let co_owner = Rc::clone(&owner);
println!("strong: {}", Rc::strong_count(&owner)); // 2
println!("weak: {}", Rc::weak_count(&owner)); // 0
// Downgrade to weak
let weak: Weak<String> = Rc::downgrade(&owner);
println!("weak: {}", Rc::weak_count(&owner)); // 1
// Drop one strong owner
drop(co_owner);
println!("strong: {}", Rc::strong_count(&owner)); // 1
// Upgrade weak -> Some because one strong owner remains
println!("upgrade: {:?}", weak.upgrade());
// Drop the last strong owner
drop(owner);
// Upgrade weak -> None because the value is gone
println!("upgrade after drop: {:?}", weak.upgrade());
}strong: 2
weak: 0
weak: 1
strong: 1
upgrade: Some("shared data")
upgrade after drop: NoneRc<T> whenever multiple parts of a single-threaded program need to share ownership of the same data. Pair it with RefCell<T> for shared mutation, and use Weak<T> to break cycles. For multi-threaded code, reach for Arc<T> instead.