Rust 57 🦀 Reference Counting Smart Pointer (Rc)
Ownership in Rust is single. Every value has exactly one owner, and when the owner goes out of scope, the value is dropped. This rule is what makes the borrow checker predictable, but it breaks down when a value must be shared. A graph node referenced by several edges, a tree node with two parents, or a configuration object held by many components: these need multiple owners for the same value. The Rc<T> smart pointer solves this. It keeps a count of how many owners a value has, and it drops the value only when the count reaches zero. The count is not atomic, which makes Rc cheap but single-threaded, and the value behind it is immutable by default, which means mutation requires an interior-mutability type like RefCell.
Key point: Rc<T> provides shared ownership on a single thread. The reference count is a plain integer, incremented and decremented on clone and drop. It is not atomic, so it is not thread-safe. For multithreaded sharing, use Arc<T>.
Why Rc exists
The multiple-owner problem. The ownership rules allow exactly one owner for a value. Some data structures need more. A node in a doubly linked list has a next pointer and a prev pointer, and both point to nodes that are also pointed to by other nodes. The ownership graph has cycles, or at least multiple parents, and a strict tree of owners cannot represent it.
The shared-immutable problem. A configuration object is read by many components and mutated by none. The components need references to the same value, and the value must live as long as any of them holds a reference. Rc<T> provides exactly this: shared immutable access with automatic lifetime extension.
The clone problem. Cloning a large value copies it. Cloning an Rc<T> copies only the pointer and increments the count. The shared value is not duplicated, which is what makes Rc cheap even when the underlying value is large.
The drop problem. An Rc<T> value is dropped when the last Rc pointing to it is dropped. The reference count is what tracks this. If the count is wrong, the value is either leaked (dropped too late) or freed while still in use (dropped too early). The Rc implementation maintains the count correctly.
The cycle problem. An Rc<T> cycle leaks. If two Rc values point to each other, each holds the other’s count above zero, and neither is dropped. The Weak<T> type breaks the cycle by holding a reference that does not contribute to the count.
The thread-safety problem. The Rc count is a Cell<usize>, which is not atomic. Incrementing and decrementing from two threads is a data race. The Arc<T> type uses an AtomicUsize, which is safe but slower. Rc is the single-threaded choice.
The interior-mutability problem. Rc<T> allows shared access but not mutable access. Rc<RefCell<T>> combines the two: the Rc provides shared ownership, and the RefCell provides runtime borrow checking. The combination is the standard pattern for mutable shared data on a single thread.
a. The Rc API
Rc<T> is a smart pointer that wraps a value and a reference count. The pointer is created with Rc::new, cloned with Rc::clone, and inspected with Rc::strong_count and Rc::weak_count.
The creation
use std::rc::Rc;
fn main() {
let value = Rc::new(5);
println!("{}", value); // 5
println!("{}", Rc::strong_count(&value)); // 1
}
The Rc::new function allocates the value on the heap and initializes the strong count to 1. The value is accessed through the Rc, which derefs to the inner value.
The clone
fn main() {
let a = Rc::new(5);
let b = Rc::clone(&a);
println!("{}", Rc::strong_count(&a)); // 2
}
The Rc::clone function is the idiomatic way to clone. It is a function call, not a method, and it is deliberately named to distinguish it from a.clone(), which would also clone the inner value if the inner value implements Clone.
The strong count
fn main() {
let a = Rc::new(5);
let b = Rc::clone(&a);
let c = Rc::clone(&a);
println!("{}", Rc::strong_count(&a)); // 3
}
The Rc::strong_count function returns the number of Rc values that own the same allocation.
The drop
fn main() {
let a = Rc::new(5);
let b = Rc::clone(&a);
println!("{}", Rc::strong_count(&a)); // 2
drop(b);
println!("{}", Rc::strong_count(&a)); // 1
}
The drop function decrements the count. When the count reaches zero, the value is dropped.
The deref
fn main() {
let a = Rc::new(vec![1, 2, 3]);
println!("{}", a.len()); // 3, deref to Vec
}
The Rc<T> implements Deref<Target = T>, so method calls and field accesses reach the inner value.
The try_unwrap
fn main() {
let a = Rc::new(5);
let value = Rc::try_unwrap(a).ok();
println!("{:?}", value); // Some(5)
}
The Rc::try_unwrap function returns the inner value if the count is 1. If the count is greater, it returns the Rc in an Err.
The into_raw and from_raw
use std::rc::Rc;
fn main() {
let a = Rc::new(5);
let raw = Rc::into_raw(a);
let b = unsafe { Rc::from_raw(raw) };
println!("{}", b); // 5
}
The into_raw function consumes the Rc and returns the raw pointer without decrementing the count. The from_raw function reconstructs the Rc from the pointer. The pair is used in unsafe code that needs to pass the pointer through a C API.
The ptr_eq
use std::rc::Rc;
use std::ptr;
fn main() {
let a = Rc::new(5);
let b = Rc::clone(&a);
let c = Rc::new(5);
println!("{}", Rc::ptr_eq(&a, &b)); // true
println!("{}", Rc::ptr_eq(&a, &c)); // false
}
The Rc::ptr_eq function returns true when two Rc values point to the same allocation. The == operator compares the inner values, which is a different check.
b. The Rc patterns
Rc<T> is used in a small set of recurring patterns. The patterns are the reason the type exists.
The shared immutable data
use std::rc::Rc;
struct Config {
name: String,
version: u32,
}
fn main() {
let config = Rc::new(Config {
name: "app".to_string(),
version: 1,
});
let a = Rc::clone(&config);
let b = Rc::clone(&config);
println!("{}", a.name);
println!("{}", b.version);
}
The Config is created once and shared by reference. The value is not duplicated.
The Rc in a struct field
use std::rc::Rc;
struct Node {
value: i32,
next: Option<Rc<Node>>,
}
fn main() {
let second = Rc::new(Node { value: 2, next: None });
let first = Rc::new(Node { value: 1, next: Some(Rc::clone(&second)) });
println!("{}", first.value);
println!("{}", first.next.as_ref().unwrap().value);
}
The next field is an Option<Rc<Node>>, which allows a node to point to another node. The Rc provides the shared ownership.
The Rc with RefCell
use std::rc::Rc;
use std::cell::RefCell;
fn main() {
let value = Rc::new(RefCell::new(5));
let a = Rc::clone(&value);
let b = Rc::clone(&value);
*a.borrow_mut() += 1;
*b.borrow_mut() += 1;
println!("{}", value.borrow()); // 7
}
The Rc<RefCell<T>> combination provides shared mutable access. The RefCell enforces the borrow rules at runtime.
The Rc in a tree
use std::rc::Rc;
struct TreeNode {
value: i32,
children: Vec<Rc<TreeNode>>,
}
fn main() {
let leaf = Rc::new(TreeNode { value: 1, children: vec![] });
let root = Rc::new(TreeNode {
value: 0,
children: vec![Rc::clone(&leaf)],
});
println!("{}", root.children[0].value);
}
The tree’s children are Vec<Rc<TreeNode>>. A node can be shared by multiple parents, which the strict ownership model cannot represent.
The Rc cycle
use std::rc::Rc;
use std::cell::RefCell;
struct Node {
value: i32,
next: RefCell<Option<Rc<Node>>>,
}
fn main() {
let a = Rc::new(Node { value: 1, next: RefCell::new(None) });
let b = Rc::new(Node { value: 2, next: RefCell::new(Some(Rc::clone(&a))) });
*a.next.borrow_mut() = Some(Rc::clone(&b));
// The cycle leaks: neither a nor b is dropped.
}
The cycle is the classic Rc leak. The Weak<T> type is the fix.
The Rc with Weak
use std::rc::{Rc, Weak};
use std::cell::RefCell;
struct Node {
value: i32,
parent: RefCell<Weak<Node>>,
children: RefCell<Vec<Rc<Node>>>,
}
fn main() {
let parent = Rc::new(Node {
value: 1,
parent: RefCell::new(Weak::new()),
children: RefCell::new(vec![]),
});
let child = Rc::new(Node {
value: 2,
parent: RefCell::new(Rc::downgrade(&parent)),
children: RefCell::new(vec![]),
});
parent.children.borrow_mut().push(Rc::clone(&child));
if let Some(p) = child.parent.borrow().upgrade() {
println!("{}", p.value); // 1
}
}
The Weak reference does not contribute to the strong count, so the cycle does not leak. The upgrade method returns an Option<Rc<T>> that is Some if the value is still alive.
The Rc pattern table
| Pattern | Type | Use Case |
|---|---|---|
| Shared immutable | Rc<T> | Configuration, constants |
| Linked structure | Rc<Node> | Trees, graphs without cycles |
| Shared mutable | Rc<RefCell<T>> | Single-threaded mutable state |
| Cycle-safe | Rc<T> + Weak<T> | Parent pointers, back references |
c. Rc versus other pointers
Rc<T> is one of several smart pointers. The choice depends on the ownership and the threading requirements.
The Rc versus Box
Box<T> has a single owner. Rc<T> has multiple owners. A Box is the default when the value has one owner; an Rc is used when the value must be shared.
The Rc versus Arc
Arc<T> is the atomic version of Rc<T>. The count is an AtomicUsize, so it is safe to share across threads. The cost is a slower increment and decrement, which matters in tight loops. The rule is: use Rc on a single thread, Arc across threads.
The Rc versus RefCell
Rc<T> provides shared access and does not allow mutation through the Rc itself. RefCell<T> provides interior mutability with runtime borrow checking. The two solve different problems, and they are often combined as Rc<RefCell<T>>.
The Rc versus Mutex
Mutex<T> provides mutable access across threads. It is the multithreaded counterpart of RefCell, and it is usually paired with Arc as Arc<Mutex<T>>.
The Rc versus a reference
A reference &T is a borrowed view with a lifetime. An Rc<T> is an owned shared value with no lifetime. The reference is cheaper but requires the borrow checker to prove the lifetime. The Rc is more flexible but has the counting overhead.
The pointer comparison
| Pointer | Ownership | Thread-safe | Mutable | Overhead |
|---|---|---|---|---|
Box<T> | Single | If T: Send | If T: Mut | One allocation |
Rc<T> | Shared | No | No | Reference count |
Arc<T> | Shared | Yes | No | Atomic count |
RefCell<T> | Single | No | Yes | Borrow flag |
Mutex<T> | Single | Yes | Yes | Lock |
&T | Borrowed | If T: Sync | No | None |
The Rc<RefCell> combination
The Rc<RefCell<T>> combination is the standard pattern for shared mutable state on a single thread. The Rc provides the shared ownership, and the RefCell provides the runtime-checked mutation.
use std::rc::Rc;
use std::cell::RefCell;
fn main() {
let counter = Rc::new(RefCell::new(0));
let a = Rc::clone(&counter);
let b = Rc::clone(&counter);
*a.borrow_mut() += 1;
*b.borrow_mut() += 1;
println!("{}", counter.borrow()); // 2
}
The borrow rules are checked at runtime. A double mutable borrow panics, which is the trade-off for the flexibility.
The Arc<Mutex> combination
The Arc<Mutex<T>> combination is the multithreaded counterpart.
use std::sync::{Arc, Mutex};
use std::thread;
fn main() {
let counter = Arc::new(Mutex::new(0));
let mut handles = vec![];
for _ in 0..10 {
let c = Arc::clone(&counter);
handles.push(thread::spawn(move || {
*c.lock().unwrap() += 1;
}));
}
for h in handles {
h.join().unwrap();
}
println!("{}", counter.lock().unwrap()); // 10
}
The Arc provides the shared ownership, and the Mutex provides the thread-safe mutation.
The performance consideration
Rc<T> is cheap but not free. Each clone increments the count, and each drop decrements it. The count is a memory operation, and the value is on the heap. For a hot loop, the cost is measurable, and a reference is faster.
The lifetime consideration
Rc<T> does not have a lifetime parameter. The value lives as long as any Rc points to it, and the compiler does not track the lifetime. The runtime count does the work that the borrow checker would do at compile time.
Complete Example Session
// ============================================
// PART 1: THE BASIC RC
// ============================================
use std::rc::Rc;
fn main() {
let a = Rc::new(5);
let b = Rc::clone(&a);
println!("{}", Rc::strong_count(&a)); // 2
println!("{}", *a); // 5
}
// ============================================
// PART 2: THE COUNT
// ============================================
fn main() {
let a = Rc::new(5);
assert_eq!(Rc::strong_count(&a), 1);
let b = Rc::clone(&a);
assert_eq!(Rc::strong_count(&a), 2);
drop(b);
assert_eq!(Rc::strong_count(&a), 1);
}
// ============================================
// PART 3: THE SHARED CONFIG
// ============================================
struct Config {
name: String,
version: u32,
}
fn main() {
let config = Rc::new(Config {
name: "app".to_string(),
version: 1,
});
let a = Rc::clone(&config);
let b = Rc::clone(&config);
println!("{} v{}", a.name, b.version);
}
// ============================================
// PART 4: THE LINKED LIST
// ============================================
struct Node {
value: i32,
next: Option<Rc<Node>>,
}
fn main() {
let second = Rc::new(Node { value: 2, next: None });
let first = Rc::new(Node { value: 1, next: Some(Rc::clone(&second)) });
println!("{}", first.next.as_ref().unwrap().value); // 2
}
// ============================================
// PART 5: THE RC WITH REFCELL
// ============================================
use std::cell::RefCell;
fn main() {
let value = Rc::new(RefCell::new(5));
let a = Rc::clone(&value);
let b = Rc::clone(&value);
*a.borrow_mut() += 1;
*b.borrow_mut() += 1;
println!("{}", value.borrow()); // 7
}
// ============================================
// PART 6: THE TREE
// ============================================
struct TreeNode {
value: i32,
children: Vec<Rc<TreeNode>>,
}
fn main() {
let leaf = Rc::new(TreeNode { value: 1, children: vec![] });
let root = Rc::new(TreeNode {
value: 0,
children: vec![Rc::clone(&leaf)],
});
println!("{}", root.children[0].value);
}
// ============================================
// PART 7: THE CYCLE LEAK
// ============================================
struct CyclicNode {
value: i32,
next: RefCell<Option<Rc<CyclicNode>>>,
}
fn main() {
let a = Rc::new(CyclicNode { value: 1, next: RefCell::new(None) });
let b = Rc::new(CyclicNode { value: 2, next: RefCell::new(Some(Rc::clone(&a))) });
*a.next.borrow_mut() = Some(Rc::clone(&b));
// The cycle leaks. Neither a nor b is dropped.
}
// ============================================
// PART 8: THE WEAK REFERENCE
// ============================================
use std::rc::Weak;
struct ParentNode {
value: i32,
parent: RefCell<Weak<ParentNode>>,
children: RefCell<Vec<Rc<ParentNode>>>,
}
fn main() {
let parent = Rc::new(ParentNode {
value: 1,
parent: RefCell::new(Weak::new()),
children: RefCell::new(vec![]),
});
let child = Rc::new(ParentNode {
value: 2,
parent: RefCell::new(Rc::downgrade(&parent)),
children: RefCell::new(vec![]),
});
parent.children.borrow_mut().push(Rc::clone(&child));
if let Some(p) = child.parent.borrow().upgrade() {
println!("{}", p.value); // 1
}
}
// ============================================
// PART 9: THE TRY_UNWRAP
// ============================================
fn main() {
let a = Rc::new(5);
match Rc::try_unwrap(a) {
Ok(value) => println!("{}", value), // 5
Err(_) => println!("Still shared"),
}
}
// ============================================
// PART 10: THE COMPARISON
// ============================================
use std::sync::Arc;
use std::sync::Mutex;
fn single_thread() {
let data = Rc::new(RefCell::new(0));
*data.borrow_mut() += 1;
}
fn multi_thread() {
let data = Arc::new(Mutex::new(0));
*data.lock().unwrap() += 1;
}
The ten parts showed the basic Rc, the count, the shared config, the linked list, the Rc<RefCell<T>> combination, the tree, the cycle leak, the Weak reference, the try_unwrap, and the comparison with Arc<Mutex<T>>.
Quick Reference
The Rc API
| Method | Purpose |
|---|---|
Rc::new(value) | Create a new Rc |
Rc::clone(&rc) | Increment the count |
Rc::strong_count(&rc) | The strong count |
Rc::weak_count(&rc) | The weak count |
Rc::try_unwrap(rc) | Extract if unique |
Rc::ptr_eq(&a, &b) | Same allocation |
Rc::into_raw(rc) | Consume to a raw pointer |
Rc::from_raw(p) | Reconstruct from a raw pointer |
Rc::downgrade(&rc) | Create a Weak |
The Weak API
| Method | Purpose |
|---|---|
Weak::new() | Create an empty Weak |
Rc::downgrade(&rc) | Create a Weak from an Rc |
weak.upgrade() | Return Option<Rc<T>> |
weak.strong_count() | The strong count |
weak.weak_count() | The weak count |
The Pointer Comparison
| Pointer | Ownership | Thread-safe | Mutable |
|---|---|---|---|
Box<T> | Single | If T: Send | If T: Mut |
Rc<T> | Shared | No | No |
Arc<T> | Shared | Yes | No |
RefCell<T> | Single | No | Yes |
Mutex<T> | Single | Yes | Yes |
The Common Combinations
| Combination | Use Case |
|---|---|
Rc<T> | Shared immutable |
Rc<RefCell<T>> | Shared mutable, single thread |
Rc<Weak<T>> | Back references, cycle-safe |
Arc<Mutex<T>> | Shared mutable, multi thread |
Arc<RwLock<T>> | Shared read-mostly, multi thread |
The Cycle Indicators
| Symptom | Cause | Fix |
|---|---|---|
| Memory never freed | Cycle of strong refs | Use Weak for back edges |
| Count never reaches zero | Cycle | Break the cycle |
Panic on borrow_mut | Double mutable borrow | Shorten the borrow |
Best Practices
✅ Do This:
// Use Rc for shared immutable data.
let config = Rc::new(Config { ... }); // ✅
// Use Rc::clone for cloning.
let b = Rc::clone(&a); // ✅
// Use Weak for back references.
parent: RefCell::new(Rc::downgrade(&parent)) // ✅
// Use Rc<RefCell<T>> for shared mutable state.
let counter = Rc::new(RefCell::new(0)); // ✅
// Use Arc for multithreaded sharing.
let data = Arc::new(Mutex::new(0)); // ✅
// Check the count when debugging leaks.
println!("{}", Rc::strong_count(&a)); // ✅
// Use upgrade() to check if a Weak is still alive.
if let Some(p) = weak.upgrade() { } // ✅
❌ Don’t Do This:
// Don't use Rc across threads.
let a = Rc::new(5);
thread::spawn(move || { *a }); // ❌ Rc is not Send
// Don't create a cycle of strong references.
a.next = Some(Rc::clone(&b));
b.next = Some(Rc::clone(&a)); // ❌ Leaks
// Don't use Rc when a reference suffices.
fn process(data: Rc<Vec<i32>>) { } // ❌ Use &[i32]
// Don't clone the inner value.
let b = (*a).clone(); // ❌ Clones the Vec, not the Rc
// Don't hold a RefCell borrow across a function call.
let r = cell.borrow();
other_function(); // ❌ May panic on borrow_mut
// Don't use Rc for a single-owner value.
let x = Rc::new(5); // ⚠️ Box is simpler
Common Pitfalls
| Pitfall | Why It Happens | Fix |
|---|---|---|
| Memory leak | Cycle of Rc | Use Weak for back edges |
Not Send | Rc used across threads | Use Arc |
| Panic on borrow | Double borrow_mut | Shorten the borrow |
| Count not zero | Cycle or forgotten ref | Find the cycle |
| Slow clone | Cloning inner value | Use Rc::clone |
| Reference cycle | Parent and child point to each other | Weak for one direction |
try_unwrap fails | Other Rc values exist | Drop them first |
Real-World Examples
1. Basic Rc
let a = Rc::new(5);
let b = Rc::clone(&a);
2. Shared Config
let config = Rc::new(Config { name: "app".into() });
3. Rc in a Struct
struct Node { next: Option<Rc<Node>> }
4. Rc<RefCell>
let value = Rc::new(RefCell::new(5));
*value.borrow_mut() += 1;
5. Tree
children: Vec<Rc<TreeNode>>
6. Weak Reference
parent: RefCell::new(Weak::new())
7. Downgrade
Rc::downgrade(&parent)
8. Upgrade
if let Some(p) = weak.upgrade() { }
9. Strong Count
println!("{}", Rc::strong_count(&a));
10. Arc<Mutex>
let data = Arc::new(Mutex::new(0));
Visual
The Rc Structure
┌────────────────────────────────────────────────────────────┐
│ THE Rc STRUCTURE │
│ │
│ let a = Rc::new(5); │
│ let b = Rc::clone(&a); │
│ let c = Rc::clone(&a); │
│ │
│ STACK HEAP │
│ ┌──────────┐ ┌──────────────────────┐ │
│ │ a: ptr ──┼──────────────────▶│ strong: 3 │ │
│ ├──────────┤ │ weak: 0 │ │
│ │ b: ptr ──┼──────────────────▶│ value: 5 │ │
│ ├──────────┤ └──────────────────────┘ │
│ │ c: ptr ──┼──────────────────▶ │
│ └──────────┘ │
│ │
│ The three Rc values share the same heap allocation. │
│ The strong count is 3. │
│ The value is dropped when the count reaches 0. │
└────────────────────────────────────────────────────────────┘
The Clone and Drop
┌────────────────────────────────────────────────────────────┐
│ CLONE AND DROP │
│ │
│ let a = Rc::new(5); strong = 1 │
│ let b = Rc::clone(&a); strong = 2 │
│ let c = Rc::clone(&a); strong = 3 │
│ │
│ drop(b); strong = 2 │
│ drop(c); strong = 1 │
│ drop(a); strong = 0 → value dropped │
│ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ Each clone increments the count. │ │
│ │ Each drop decrements the count. │ │
│ │ The value is dropped when the count reaches 0. │ │
│ └──────────────────────────────────────────────────┘ │
└────────────────────────────────────────────────────────────┘
The Cycle Leak
┌────────────────────────────────────────────────────────────┐
│ THE CYCLE LEAK │
│ │
│ let a = Rc::new(Node { next: None }); │
│ let b = Rc::new(Node { next: Some(Rc::clone(&a)) }); │
│ *a.next.borrow_mut() = Some(Rc::clone(&b)); │
│ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ a ──────────────▶ b │ │
│ │ ▲ │ │ │
│ │ │ │ │ │
│ │ └─────────────────┘ │ │
│ │ │ │
│ │ a's count: 2 (b.next and a) │ │
│ │ b's count: 2 (a.next and b) │ │
│ │ │ │
│ │ When the scope ends, a's count drops to 1, │ │
│ │ and b's count drops to 1. Neither reaches 0. │ │
│ │ The cycle leaks. │ │
│ └──────────────────────────────────────────────────┘ │
│ │
│ FIX: use Weak for one direction. │
└────────────────────────────────────────────────────────────┘
The Rc<RefCell> Pattern
┌────────────────────────────────────────────────────────────┐
│ Rc<RefCell<T>> PATTERN │
│ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ Rc<T> │ │
│ │ Shared ownership │ │
│ │ Multiple owners │ │
│ │ Immutable access │ │
│ └────────────────────┬─────────────────────────────┘ │
│ │ │
│ │ combined with │
│ v │
│ ┌──────────────────────────────────────────────────┐ │
│ │ RefCell<T> │ │
│ │ Interior mutability │ │
│ │ Runtime borrow checking │ │
│ │ Single-threaded │ │
│ └────────────────────┬─────────────────────────────┘ │
│ │ │
│ v │
│ ┌──────────────────────────────────────────────────┐ │
│ │ Rc<RefCell<T>> │ │
│ │ Shared mutable state │ │
│ │ Single-threaded │ │
│ │ Panics on double mutable borrow │ │
│ └──────────────────────────────────────────────────┘ │
└────────────────────────────────────────────────────────────┘
Summary
| Item | Value |
|---|---|
Rc<T> | Shared ownership on a single thread |
Rc::new | Create a new Rc |
Rc::clone | Increment the count |
Rc::strong_count | The number of owners |
Rc::weak_count | The number of weak refs |
Rc::try_unwrap | Extract if unique |
Rc::ptr_eq | Same allocation |
Weak<T> | A non-owning reference |
Rc<RefCell<T>> | Shared mutable state |
Arc<T> | The thread-safe counterpart |
Key takeaways:
Rc<T>provides shared ownership on a single thread. The reference count tracks how many owners a value has, and the value is dropped when the count reaches zero. The count is not atomic, so the type is not thread-safe.Rc::cloneincrements the count;dropdecrements it. The clone is cheap because it copies a pointer, not the value. The value is shared, not duplicated.- The deref makes
Rc<T>transparent. Method calls and field accesses reach the inner value, so theRclooks like the value it wraps. - A cycle of
Rcvalues leaks. TwoRcvalues pointing to each other keep each other’s count above zero. TheWeak<T>type breaks the cycle by holding a non-owning reference. Rc<RefCell<T>>is the shared-mutable pattern. TheRcprovides the shared ownership, and theRefCellprovides the runtime-checked mutation. The combination is single-threaded and panics on a double mutable borrow.Arc<T>is the thread-safe counterpart. It uses an atomic count, which is safe across threads but slower. The rule is:Rcon a single thread,Arcacross threads.Rc::try_unwrapextracts the value if the count is 1. The function is used when the value is no longer shared and the inner value is needed by value.Rc::ptr_eqcompares the allocation, not the value. The==operator compares the inner values, which is a different check.- The
Weakreference does not contribute to the strong count. Theupgrademethod returnsSome(Rc<T>)if the value is still alive, orNoneif it has been dropped.
Remember: Rc<T> is the smart pointer for shared ownership on a single thread. It keeps a count of how many owners a value has, and it drops the value when the count reaches zero. The clone is cheap because it copies a pointer, and the deref makes the Rc transparent. The Rc<RefCell<T>> combination provides shared mutable state, and the Weak<T> type breaks cycles. The type is not thread-safe, so it cannot be sent across threads; the Arc<T> type is the multithreaded counterpart. The result is shared ownership without the borrow checker’s single-owner rule, and automatic cleanup without a garbage collector.
Stop using slow, ad-bloated tool sites! 🤮
🔎 Search “KandZ Tools” on Google to use many professional utilities for free.
KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)
⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free
🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!