TL;DR:
Rc<T>(Reference Counted) lets multiple parts of single-threaded code share ownership of a value. It tracks how many clones exist; when the count reaches zero the value is dropped. UnlikeArc<T>,Rcis not thread-safe, it has no atomic overhead. UseRcfor single-threaded shared ownership (tree nodes, graph edges),Arcwhen you need to share across threads.
What Is Rc<T> in Rust?
Rc<T> is a smart pointer that enables multiple-owner shared access to a heap-allocated value within a single thread, tracked via reference counting.
use std::rc::Rc;
fn main() {
let a = Rc::new(String::from("shared value"));
let b = Rc::clone(&a); // increments reference count; no data copy
let c = Rc::clone(&a);
println!("ref count: {}", Rc::strong_count(&a)); // 3
println!("{a}, {b}, {c}");
drop(c);
println!("ref count after drop: {}", Rc::strong_count(&a)); // 2
} // a and b drop here; count reaches 0; value is freedRc::clone is cheap, it just increments a counter. The actual data is stored once on the heap.
How Is Rc Different From Arc?
Rc uses non-atomic reference counting (cheaper, single-thread only). Arc uses atomic operations (thread-safe but slightly more expensive). The API is identical.
Rc<T> | Arc<T> | |
|---|---|---|
| Thread-safe | ❌ (!Send, !Sync) | ✅ |
| Reference count | Non-atomic (cheap) | Atomic (small overhead) |
| Use case | Single-threaded sharing | Multi-threaded sharing |
| Combined with | RefCell<T> | Mutex<T> or RwLock<T> |
use std::rc::Rc;
use std::sync::Arc;
// Rc; fine in single-threaded code
let rc = Rc::new(42);
// Arc; required when moving across thread boundaries
let arc = Arc::new(42);
std::thread::spawn(move || println!("{arc}")); // Arc is Send
// std::thread::spawn(move || println!("{rc}")); // Error: Rc is !SendHow Do You Get Mutability With Rc?
Rc<T> gives shared (immutable) access. For mutation, combine it with RefCell<T>, this is the Rc<RefCell<T>> pattern for interior mutability in single-threaded code.
use std::rc::Rc;
use std::cell::RefCell;
fn main() {
let shared = Rc::new(RefCell::new(vec![1, 2, 3]));
let a = Rc::clone(&shared);
let b = Rc::clone(&shared);
// Multiple owners, all can mutate
a.borrow_mut().push(4);
b.borrow_mut().push(5);
println!("{:?}", shared.borrow()); // [1, 2, 3, 4, 5]
}Borrow rules are enforced at runtime (panics if you have two mutable borrows simultaneously), not compile time.
When Should You Use Rc?
Use Rc when you have a data structure where ownership isn't clear, multiple parts need equal ownership of the same data, all within one thread.
Common use cases:
- Tree/graph nodes that may have multiple parents
- Shared config or read-only data within one thread
- Implementing a simple arena or reference graph
- GUI frameworks where widgets may reference shared state
use std::rc::Rc;
// Linked list node that may be referenced from multiple places
struct Node {
value: i32,
children: Vec<Rc<Node>>,
}
fn main() {
let leaf = Rc::new(Node { value: 1, children: vec![] });
// Two parent nodes share the same leaf
let parent_a = Node { value: 2, children: vec![Rc::clone(&leaf)] };
let parent_b = Node { value: 3, children: vec![Rc::clone(&leaf)] };
}What Are Weak References?
Rc::downgrade creates a Weak<T>, a non-owning reference that doesn't prevent the value from being dropped. Use Weak to break reference cycles.
use std::rc::{Rc, Weak};
use std::cell::RefCell;
struct Node {
value: i32,
parent: Option<Weak<RefCell<Node>>>, // weak; doesn't keep parent alive
children: Vec<Rc<RefCell<Node>>>,
}If you have Rc<A> pointing to B and Rc<B> pointing back to A, neither will ever be dropped, a cycle. Make one direction Weak to break the cycle.
Frequently Asked Questions
Small overhead: each clone/drop increments/decrements a counter (non-atomic, very fast). Dereferencing Rc<T> itself has no overhead, it's just a pointer dereference. The main cost is the extra heap allocation for the reference count alongside the data.
No, Rc<T> is !Send and !Sync. The compiler will reject any attempt to send it to another thread. Use Arc<T> for thread-safe shared ownership.
Circular Rc references create memory leaks, the count never reaches zero, so the values are never dropped. Use Weak<T> for back-references in data structures to break cycles.
Yes, Rc is 100% safe Rust. Reference cycles cause memory leaks (not undefined behavior), and RefCell panics (not undefined behavior) if borrow rules are violated at runtime.
Sources
- std::rc::Rc: Standard library documentation
- The Rust Book ;
Rc<T> - The Rust Book ; RefCell and Interior Mutability
Related Glossary Terms
- Arc: The thread-safe version of Rc
- RefCell: Combined with Rc for interior mutability
- Box: Single-ownership heap allocation (simpler than Rc)
- Ownership: Rc exists to work around single-ownership restrictions
Keep Reading
- Rust Ownership and Borrowing Explained: Rc is a single-threaded alternative to Arc for shared ownership
- Rust Memory Safety: NSA and CISA: reference counting as a memory management strategy
- Rust vs Python Performance: Python's garbage collector uses reference counting internally
