Rust 59 🦀 Preventing Memory Leaks and Reference Cycles (Weak)
Rust’s ownership rules guarantee that a value is dropped when its owner goes out of scope. The guarantee holds for trees, stacks, and every structure where ownership is acyclic. It breaks for cycles. When two Rc<T> values point to each other, each keeps the other’s strong count above zero, and neither is dropped. The memory is leaked, and no amount of scope management recovers it. The Weak<T> type is the solution. It holds a reference to the same allocation without incrementing the strong count, so the cycle is broken and the values are dropped when the last strong reference goes away. The Weak<T> is the counterpart of Rc<T> in the same way that a borrowed reference is the counterpart of an owned value: it observes the value without keeping it alive.
Key point: A Weak<T> reference does not own the value. The value is dropped when the last Rc<T> is dropped, regardless of how many Weak<T> references exist. The upgrade method converts a Weak<T> to an Option<Rc<T>>, which is Some if the value is still alive and None if it has been dropped.
Why Weak exists
The cycle problem. An Rc<T> cycle is a leak. Two nodes pointing to each other, a parent-child relationship with a back pointer, or a doubly linked list — all of these create cycles that keep each other’s strong counts above zero. The values are never dropped, and the memory is never freed.
The back-pointer problem. A tree node has children and a pointer to its parent. The parent owns the children, and the children need to know their parent. If the child holds a strong reference to the parent, the cycle forms. The fix is to make the back pointer weak, because the parent’s lifetime is longer than the child’s.
The observer problem. A subject holds a list of observers, and each observer may hold a reference to the subject. If the reference is strong, the cycle forms. The Weak<T> in the observer’s reference breaks the cycle.
The cache problem. A cache holds references to values that may be dropped by their owners. The cache should not keep the values alive, or it becomes a memory leak. A Weak<T> cache holds references that disappear when the value is dropped.
The parent-child problem. A parent owns a child, and the child points back to the parent. The parent’s reference is strong; the child’s reference is weak. The distinction matches the ownership: the parent owns the child, so the parent’s reference keeps the child alive; the child does not own the parent, so the child’s reference does not keep the parent alive.
The thread-safety problem. The Weak<T> type has a thread-safe counterpart, std::sync::Weak<T>, which pairs with Arc<T>. The two are not interchangeable: Rc and its Weak are single-threaded, and Arc and its Weak are thread-safe.
The dangling problem. A Weak<T> reference can outlive the value it points to. The upgrade method returns an Option<Rc<T>> that is None if the value has been dropped. The caller must handle both cases, which is the price of the non-owning reference.
a. The Weak API
Weak<T> is created from an Rc<T> with Rc::downgrade, and it is converted back with the upgrade method. The Weak does not contribute to the strong count, but it does contribute to the weak count.
The downgrade
use std::rc::{Rc, Weak};
fn main() {
let strong = Rc::new(5);
let weak: Weak<i32> = Rc::downgrade(&strong);
println!("strong: {}", Rc::strong_count(&strong)); // 1
println!("weak: {}", Rc::weak_count(&strong)); // 1
}
The Rc::downgrade function creates a Weak reference. The strong count stays at 1, and the weak count becomes 1.
The upgrade
fn main() {
let strong = Rc::new(5);
let weak = Rc::downgrade(&strong);
if let Some(rc) = weak.upgrade() {
println!("{}", rc); // 5
} else {
println!("Value has been dropped");
}
}
The upgrade method returns Some(Rc<T>) if the value is still alive, and None if it has been dropped. The returned Rc increments the strong count.
The strong_count and weak_count
fn main() {
let strong = Rc::new(5);
let weak = Rc::downgrade(&strong);
let _ = weak.upgrade();
println!("strong: {}", Rc::strong_count(&strong)); // 2
println!("weak: {}", Rc::weak_count(&strong)); // 1
}
The upgrade call creates a temporary Rc that increments the strong count. The count is decremented when the Rc is dropped.
The after-drop behavior
fn main() {
let strong = Rc::new(5);
let weak = Rc::downgrade(&strong);
drop(strong);
println!("{}", weak.upgrade().is_none()); // true
}
When the last Rc is dropped, the value is dropped. The upgrade method then returns None.
The new
use std::rc::Weak;
fn main() {
let weak: Weak<i32> = Weak::new();
println!("{}", weak.upgrade().is_none()); // true
}
The Weak::new function creates a Weak reference that points to nothing. The upgrade always returns None.
The ptr_eq
fn main() {
let a = Rc::new(5);
let weak_a = Rc::downgrade(&a);
let b = Rc::new(5);
let weak_b = Rc::downgrade(&b);
println!("{}", weak_a.ptr_eq(&weak_b)); // false
}
The Weak::ptr_eq function returns true if two Weak references point to the same allocation.
The as_ptr
fn main() {
let a = Rc::new(5);
let weak = Rc::downgrade(&a);
println!("{:?}", weak.as_ptr());
}
The as_ptr method returns the raw pointer to the value. The pointer is valid as long as the value is alive, but the Weak does not keep it alive.
The Weak methods
| Method | Returns |
|---|---|
Rc::downgrade(&rc) | Weak<T> |
Weak::new() | An empty Weak |
weak.upgrade() | Option<Rc<T>> |
weak.strong_count() | usize |
weak.weak_count() | usize |
weak.ptr_eq(&other) | bool |
weak.as_ptr() | *const T |
b. The cycle patterns
The Weak<T> type is used in a small set of patterns. The patterns are the reason the type exists.
The parent pointer
A tree node has children and a weak pointer to its parent.
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!("Parent value: {}", p.value); // 1
}
}
The parent owns the children strongly, and the child points to the parent weakly. The cycle is broken, and both values are dropped when the strong references go away.
The doubly linked list
A doubly linked list has next and prev pointers. The next pointers are strong, and the prev pointers are weak.
use std::rc::{Rc, Weak};
use std::cell::RefCell;
struct Node {
value: i32,
next: RefCell<Option<Rc<Node>>>,
prev: RefCell<Weak<Node>>,
}
fn main() {
let a = Rc::new(Node {
value: 1,
next: RefCell::new(None),
prev: RefCell::new(Weak::new()),
});
let b = Rc::new(Node {
value: 2,
next: RefCell::new(None),
prev: RefCell::new(Rc::downgrade(&a)),
});
*a.next.borrow_mut() = Some(Rc::clone(&b));
if let Some(prev) = b.prev.borrow().upgrade() {
println!("Previous: {}", prev.value); // 1
}
}
The forward pointers own the list, and the backward pointers observe it. The list is freed when the head is dropped.
The observer pattern
A subject holds a list of weak observers.
use std::rc::{Rc, Weak};
use std::cell::RefCell;
trait Observer {
fn notify(&self, value: i32);
}
struct Subject {
observers: RefCell<Vec<Weak<dyn Observer>>>,
value: i32,
}
impl Subject {
fn subscribe(&self, observer: &Rc<dyn Observer>) {
self.observers.borrow_mut().push(Rc::downgrade(observer));
}
fn set_value(&mut self, value: i32) {
self.value = value;
self.observers.borrow_mut().retain(|weak| {
if let Some(observer) = weak.upgrade() {
observer.notify(value);
true
} else {
false // The observer has been dropped.
}
});
}
}
The subject holds weak references, so an observer that is dropped no longer receives notifications. The list is cleaned up on each notification.
The cache pattern
A cache holds weak references to values that may be dropped.
use std::rc::{Rc, Weak};
use std::collections::HashMap;
use std::cell::RefCell;
struct Cache<K, V> {
entries: RefCell<HashMap<K, Weak<V>>>,
}
impl<K: std::hash::Hash + Eq + Clone, V> Cache<K, V> {
fn get(&self, key: &K) -> Option<Rc<V>> {
self.entries.borrow().get(key).and_then(|w| w.upgrade())
}
fn insert(&self, key: K, value: &Rc<V>) {
self.entries.borrow_mut().insert(key, Rc::downgrade(value));
}
}
The cache does not keep the values alive. When the value is dropped, the upgrade returns None, and the entry is effectively gone.
The graph with back edges
A graph node has outgoing edges (strong) and incoming edges (weak).
use std::rc::{Rc, Weak};
use std::cell::RefCell;
struct Node {
value: i32,
outgoing: RefCell<Vec<Rc<Node>>>,
incoming: RefCell<Vec<Weak<Node>>>,
}
fn main() {
let a = Rc::new(Node {
value: 1,
outgoing: RefCell::new(vec![]),
incoming: RefCell::new(vec![]),
});
let b = Rc::new(Node {
value: 2,
outgoing: RefCell::new(vec![]),
incoming: RefCell::new(vec![]),
});
a.outgoing.borrow_mut().push(Rc::clone(&b));
b.incoming.borrow_mut().push(Rc::downgrade(&a));
}
The forward edges own the reachable nodes, and the back edges observe them. The graph is freed when the entry points are dropped.
The pattern table
| Pattern | Strong | Weak |
|---|---|---|
| Parent pointer | Parent → Child | Child → Parent |
| Doubly linked list | next | prev |
| Observer | Subject → Observer? | Subject → Observer |
| Cache | Owner → Value | Cache → Value |
| Graph | Outgoing edges | Incoming edges |
c. The Weak rules and trade-offs
The Weak<T> type has specific rules and trade-offs. The rules prevent the common mistakes.
The non-owning rule
A Weak<T> does not keep the value alive. The value is dropped when the last Rc<T> is dropped, regardless of the weak count.
fn main() {
let strong = Rc::new(5);
let weak = Rc::downgrade(&strong);
drop(strong);
// The value is dropped even though weak still exists.
assert!(weak.upgrade().is_none());
}
The rule is what makes Weak the right type for back references and caches.
The upgrade rule
The upgrade method must be called before the value is used. The Weak reference cannot be dereferenced directly.
// ❌ This does not compile.
// let value = *weak;
// ✅ Upgrade first.
if let Some(rc) = weak.upgrade() {
println!("{}", *rc);
}
The upgrade increments the strong count, so the value is kept alive for the duration of the returned Rc.
The thread-safety rule
Rc and its Weak are single-threaded. Arc and its Weak are thread-safe. The two are not interchangeable.
use std::rc::Rc;
use std::thread;
// ❌ Rc is not Send.
let strong = Rc::new(5);
thread::spawn(move || { *strong });
The compiler rejects sending an Rc across threads. The Arc type is the thread-safe alternative.
The count rule
The strong count and the weak count are independent. The value is dropped when the strong count reaches zero. The allocation is freed when the weak count also reaches zero.
strong_count = 0, weak_count > 0 → value dropped, allocation kept
strong_count = 0, weak_count = 0 → value dropped, allocation freed
The allocation is kept as long as a Weak reference exists, because the Weak needs the count to check whether the value is still alive.
The leak rule
A cycle of strong references leaks. A cycle of weak references does not, because the weak references do not contribute to the strong count.
// ❌ Leaks: strong cycle.
a.next = Some(Rc::clone(&b));
b.next = Some(Rc::clone(&a));
// ✅ No leak: weak back edge.
a.next = Some(Rc::clone(&b));
b.prev = Some(Rc::downgrade(&a));
The fix is to make one edge of the cycle weak.
The performance trade-off
A Weak reference has a small overhead: the weak count is incremented and decremented, and the upgrade call checks the count. The overhead is negligible for most use cases, but it is measurable in tight loops.
The complexity trade-off
The Weak reference adds a layer of indirection. The upgrade call returns an Option, and the caller must handle the None case. The extra handling is the price of the non-owning reference.
The rule table
| Rule | Consequence |
|---|---|
| Non-owning | The value is dropped when the strong count is zero |
| Upgrade required | The Weak cannot be dereferenced directly |
| Not thread-safe | Rc/Weak are single-threaded |
| Allocation kept | The memory stays until the weak count is zero |
| No strong cycles | A weak edge breaks the cycle |
Complete Example Session
// ============================================
// PART 1: THE BASIC WEAK
// ============================================
use std::rc::{Rc, Weak};
fn main() {
let strong = Rc::new(5);
let weak: Weak<i32> = Rc::downgrade(&strong);
assert_eq!(Rc::strong_count(&strong), 1);
assert_eq!(Rc::weak_count(&strong), 1);
if let Some(rc) = weak.upgrade() {
println!("{}", rc); // 5
}
}
// ============================================
// PART 2: THE UPGRADE AFTER DROP
// ============================================
fn main() {
let strong = Rc::new(5);
let weak = Rc::downgrade(&strong);
drop(strong);
assert!(weak.upgrade().is_none());
}
// ============================================
// PART 3: THE PARENT POINTER
// ============================================
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
}
}
// ============================================
// PART 4: THE DOUBLY LINKED LIST
// ============================================
struct ListNode {
value: i32,
next: RefCell<Option<Rc<ListNode>>>,
prev: RefCell<Weak<ListNode>>,
}
fn main() {
let a = Rc::new(ListNode {
value: 1,
next: RefCell::new(None),
prev: RefCell::new(Weak::new()),
});
let b = Rc::new(ListNode {
value: 2,
next: RefCell::new(None),
prev: RefCell::new(Rc::downgrade(&a)),
});
*a.next.borrow_mut() = Some(Rc::clone(&b));
if let Some(prev) = b.prev.borrow().upgrade() {
println!("{}", prev.value); // 1
}
}
// ============================================
// PART 5: THE OBSERVER PATTERN
// ============================================
trait Observer {
fn notify(&self, value: i32);
}
struct Subject {
observers: RefCell<Vec<Weak<dyn Observer>>>,
}
impl Subject {
fn subscribe(&self, observer: &Rc<dyn Observer>) {
self.observers.borrow_mut().push(Rc::downgrade(observer));
}
fn notify_all(&self, value: i32) {
self.observers.borrow_mut().retain(|weak| {
if let Some(o) = weak.upgrade() {
o.notify(value);
true
} else {
false
}
});
}
}
// ============================================
// PART 6: THE CACHE PATTERN
// ============================================
use std::collections::HashMap;
struct Cache<V> {
entries: RefCell<HashMap<String, Weak<V>>>,
}
impl<V> Cache<V> {
fn get(&self, key: &str) -> Option<Rc<V>> {
self.entries.borrow().get(key).and_then(|w| w.upgrade())
}
fn insert(&self, key: String, value: &Rc<V>) {
self.entries.borrow_mut().insert(key, Rc::downgrade(value));
}
}
// ============================================
// PART 7: THE CYCLE LEAK FIX
// ============================================
struct CyclicNode {
value: i32,
next: RefCell<Option<Rc<CyclicNode>>>,
back: RefCell<Weak<CyclicNode>>,
}
fn main() {
let a = Rc::new(CyclicNode {
value: 1,
next: RefCell::new(None),
back: RefCell::new(Weak::new()),
});
let b = Rc::new(CyclicNode {
value: 2,
next: RefCell::new(None),
back: RefCell::new(Rc::downgrade(&a)),
});
*a.next.borrow_mut() = Some(Rc::clone(&b));
// The back pointer is weak, so the cycle does not leak.
}
// ============================================
// PART 8: THE WEAK COUNT
// ============================================
fn main() {
let strong = Rc::new(5);
let w1 = Rc::downgrade(&strong);
let w2 = Rc::downgrade(&strong);
assert_eq!(Rc::strong_count(&strong), 1);
assert_eq!(Rc::weak_count(&strong), 2);
let _upgraded = w1.upgrade();
assert_eq!(Rc::strong_count(&strong), 2);
}
// ============================================
// PART 9: THE WEAK NEW
// ============================================
fn main() {
let weak: Weak<i32> = Weak::new();
assert!(weak.upgrade().is_none());
}
// ============================================
// PART 10: THE THREAD-SAFE COUNTERPART
// ============================================
use std::sync::{Arc, Weak as SyncWeak};
fn main() {
let strong = Arc::new(5);
let weak: SyncWeak<i32> = Arc::downgrade(&strong);
if let Some(rc) = weak.upgrade() {
println!("{}", rc); // 5
}
}
The ten parts showed the basic Weak, the upgrade after drop, the parent pointer, the doubly linked list, the observer pattern, the cache pattern, the cycle leak fix, the weak count, the Weak::new, and the thread-safe counterpart.
Quick Reference
The Weak API
| Method | Returns |
|---|---|
Rc::downgrade(&rc) | Weak<T> |
Weak::new() | An empty Weak |
weak.upgrade() | Option<Rc<T>> |
weak.strong_count() | usize |
weak.weak_count() | usize |
weak.ptr_eq(&other) | bool |
weak.as_ptr() | *const T |
The Count Behavior
| State | Value | Allocation |
|---|---|---|
| strong > 0 | Alive | Allocated |
| strong = 0, weak > 0 | Dropped | Kept |
| strong = 0, weak = 0 | Dropped | Freed |
The Cycle Patterns
| Pattern | Strong Edge | Weak Edge |
|---|---|---|
| Parent pointer | Parent → Child | Child → Parent |
| Doubly linked | next | prev |
| Observer | Owner → Observer | Subject → Observer |
| Cache | Owner → Value | Cache → Value |
| Graph | Outgoing | Incoming |
The Rc vs Arc
| Single-threaded | Thread-safe |
|---|---|
Rc<T> | Arc<T> |
Weak<T> (std::rc) | Weak<T> (std::sync) |
Rc::downgrade | Arc::downgrade |
The Weak Rules
| Rule | Consequence |
|---|---|
| Non-owning | Does not keep the value alive |
| Upgrade required | Cannot be dereferenced |
| Not thread-safe | Rc/Weak are single-threaded |
| Allocation kept | The memory stays until weak count is 0 |
| Breaks cycles | A weak edge prevents the leak |
Best Practices
✅ Do This:
// Use Weak for back references.
parent: RefCell<Weak<Node>> // ✅
// Use Rc::downgrade to create a Weak.
let weak = Rc::downgrade(&strong); // ✅
// Use upgrade to access the value.
if let Some(rc) = weak.upgrade() { } // ✅
// Use Weak in caches.
cache: RefCell<HashMap<K, Weak<V>>> // ✅
// Use Weak in observer lists.
observers: RefCell<Vec<Weak<dyn Observer>>> // ✅
// Use Arc/Weak for multithreaded sharing.
let weak = Arc::downgrade(&strong); // ✅
// Clean up dead weak references.
retain(|w| w.upgrade().is_some()); // ✅
❌ Don’t Do This:
// Don't create a strong cycle.
a.next = Some(Rc::clone(&b));
b.next = Some(Rc::clone(&a)); // ❌ Leaks
// Don't dereference a Weak directly.
let value = *weak; // ❌ Does not compile
// Don't use Rc across threads.
let strong = Rc::new(5);
thread::spawn(move || { *strong }); // ❌ Not Send
// Don't forget to upgrade.
// The Weak is not the value. // ❌
// Don't hold a Weak when a strong reference is needed.
// The value may be dropped. // ❌
Common Pitfalls
| Pitfall | Why It Happens | Fix |
|---|---|---|
| Memory leak | Strong cycle | Use Weak for back edges |
upgrade returns None | Value dropped | Handle the None case |
Not Send | Rc across threads | Use Arc |
| Dangling reference | Weak outlives the value | Always upgrade |
| Cache keeps alive | Strong references in the cache | Use Weak |
| Observer leak | Strong references in the list | Use Weak |
| Count confusion | upgrade increments the strong count | Drop the Rc |
Real-World Examples
1. Basic Weak
let weak = Rc::downgrade(&strong);
2. Upgrade
if let Some(rc) = weak.upgrade() { }
3. Parent Pointer
parent: RefCell<Weak<Node>>
4. Doubly Linked List
prev: RefCell<Weak<Node>>
5. Observer
observers: RefCell<Vec<Weak<dyn Observer>>>
6. Cache
entries: RefCell<HashMap<K, Weak<V>>>
7. Cycle Fix
b.back = RefCell::new(Rc::downgrade(&a));
8. Thread-Safe
let weak = Arc::downgrade(&strong);
9. Clean Up
retain(|w| w.upgrade().is_some());
10. Weak Count
Rc::weak_count(&strong)
Visual
The Strong Cycle
┌────────────────────────────────────────────────────────────┐
│ STRONG 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 │ │
│ │ ▲ │ │ │
│ │ │ │ │ │
│ │ └─────────────────┘ │ │
│ │ │ │
│ │ strong_count(a) = 2 │ │
│ │ strong_count(b) = 2 │ │
│ │ │ │
│ │ Scope ends: a drops to 1, b drops to 1. │ │
│ │ Neither reaches 0. The cycle leaks. │ │
│ └──────────────────────────────────────────────────┘ │
└────────────────────────────────────────────────────────────┘
The Weak Fix
┌────────────────────────────────────────────────────────────┐
│ THE WEAK FIX │
│ │
│ let a = Rc::new(Node { next: None, back: Weak::new() }); │
│ let b = Rc::new(Node { │
│ next: None, │
│ back: RefCell::new(Rc::downgrade(&a)), │
│ }); │
│ *a.next.borrow_mut() = Some(Rc::clone(&b)); │
│ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ a ──────────────▶ b │ │
│ │ ▲ │ │ │
│ │ └───── weak ──────┘ │ │
│ │ │ │
│ │ strong_count(a) = 1 │ │
│ │ strong_count(b) = 2 │ │
│ │ weak_count(a) = 1 │ │
│ │ │ │
│ │ Scope ends: a's strong count drops to 0. │ │
│ │ a is dropped. b's strong count drops to 1. │ │
│ │ b is dropped. No leak. │ │
│ └──────────────────────────────────────────────────┘ │
└────────────────────────────────────────────────────────────┘
The Parent-Child Pattern
┌────────────────────────────────────────────────────────────┐
│ PARENT-CHILD PATTERN │
│ │
│ Parent │
│ ┌──────────────────────────────────────────────────┐ │
│ │ children: Vec<Rc<Node>> ← strong │ │
│ │ │ │
│ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │
│ │ │ Child 1 │ │ Child 2 │ │ Child 3 │ │ │
│ │ │ │ │ │ │ │ │ │
│ │ │ parent: │ │ parent: │ │ parent: │ │ │
│ │ │ Weak │ │ Weak │ │ Weak │ │ │
│ │ └──────────┘ └──────────┘ └──────────┘ │ │
│ └──────────────────────────────────────────────────┘ │
│ │
│ The parent owns the children strongly. │
│ The children observe the parent weakly. │
│ When the parent is dropped, the children are dropped. │
│ The children cannot outlive the parent. │
└────────────────────────────────────────────────────────────┘
The Count Behavior
┌────────────────────────────────────────────────────────────┐
│ THE COUNT BEHAVIOR │
│ │
│ strong_count = 2, weak_count = 1 │
│ ┌──────────────────────────────────────────────────┐ │
│ │ Value alive. Allocation alive. │ │
│ │ upgrade() returns Some(Rc<T>). │ │
│ └──────────────────────────────────────────────────┘ │
│ │
│ strong_count = 0, weak_count = 1 │
│ ┌──────────────────────────────────────────────────┐ │
│ │ Value dropped. Allocation kept. │ │
│ │ upgrade() returns None. │ │
│ └──────────────────────────────────────────────────┘ │
│ │
│ strong_count = 0, weak_count = 0 │
│ ┌──────────────────────────────────────────────────┐ │
│ │ Value dropped. Allocation freed. │ │
│ └──────────────────────────────────────────────────┘ │
│ │
│ The allocation is kept as long as a Weak exists, │
│ because the Weak needs the count to check the value. │
└────────────────────────────────────────────────────────────┘
Summary
| Item | Value |
|---|---|
Weak<T> | A non-owning reference to an Rc<T> |
Rc::downgrade | Create a Weak from an Rc |
weak.upgrade() | Convert to Option<Rc<T>> |
Weak::new() | An empty Weak |
weak.ptr_eq | Same allocation |
weak.strong_count | The strong count |
weak.weak_count | The weak count |
| Parent pointer | Strong down, weak up |
| Doubly linked | Strong next, weak prev |
Arc/Weak | The thread-safe counterpart |
Key takeaways:
- A
Weak<T>does not own the value. The value is dropped when the lastRc<T>is dropped, regardless of how manyWeak<T>references exist. The weak reference observes the value without keeping it alive. - The
upgrademethod is the only way to access the value. It returnsSome(Rc<T>)if the value is alive andNoneif it has been dropped. The caller must handle both cases. - The strong and weak counts are independent. The value is dropped when the strong count reaches zero. The allocation is freed when both counts reach zero.
- A cycle of strong references leaks. Two
Rcvalues pointing to each other keep each other’s strong count above zero. The fix is to make one edge of the cycle weak. - The parent-child pattern is the canonical use. The parent owns the children strongly, and the children observe the parent weakly. The children cannot outlive the parent.
- The observer and cache patterns use
Weak. An observer list or a cache holds weak references so that the values are not kept alive by the list or the cache. Rcand itsWeakare single-threaded. TheArcand itsWeakare the thread-safe counterparts. The two are not interchangeable, and the compiler enforces the distinction.- The allocation is kept as long as a
Weakexists. The weak count is what keeps the allocation alive, because theWeakneeds the count to check whether the value is still alive. - The
Weakreference is the right tool for back references. The forward references are strong, and the back references are weak. The distinction matches the ownership: the forward reference owns, and the back reference observes.
Remember: The Weak<T> type is the solution to the Rc<T> cycle problem. It holds a reference to the same allocation without incrementing the strong count, so the value is dropped when the last strong reference goes away. The upgrade method converts a Weak to an Option<Rc<T>>, which is the only way to access the value. The parent-child pattern, the doubly linked list, the observer list, and the cache all use Weak for the back references. The strong references own the structure, and the weak references observe it. The result is a data structure that can have cycles without leaking, and a memory model that is deterministic, safe, and free of 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!