Rust Reference Cycles
A reference cycle is a ring of Rc values that own each other, so no owner count can reach zero. A cycle cannot crash a program, but it quietly wastes memory.
A cycle: two values that own each other
Each Rc keeps the other's count above zero, so neither value is ever freed:
struct Node { next: RefCell<Option<Rc<Node>>> }
let a = Rc::new(Node { next: RefCell::new(None) });
let b = Rc::new(Node { next: RefCell::new(Some(Rc::clone(&a))) });
*a.next.borrow_mut() = Some(Rc::clone(&b));
// a points to b and b points to a: a cycleDowngrade makes a Weak — and adds no owner
Rc::downgrade gives you the pointer. Rc::weak_count counts pointers like it, without keeping the value alive:
use std::rc::Rc;
fn main() {
let five = Rc::new(5);
let weak = Rc::downgrade(&five);
println!("strong = {}, weak = {}", Rc::strong_count(&five), Rc::weak_count(&five));
}Upgrade gives the value back — or None
A Weak is used through .upgrade(), which returns an Option: Some while the value lives, None once it is gone — never a dangling pointer:
use std::rc::Rc;
fn main() {
let weak;
{
let five = Rc::new(5);
weak = Rc::downgrade(&five);
println!("while alive: {:?}", weak.upgrade().map(|r| *r));
}
println!("after drop: {:?}", weak.upgrade().map(|r| *r));
}The standard pattern: strong down, weak back up
Owners point strongly (Rc). Back-references point weakly (Weak). Trees and graphs use this everywhere — parents own children, children only point back:
struct Node {
value: i32,
children: RefCell<Vec<Rc<Node>>>, // strong: the parent owns its children
parent: RefCell<Weak<Node>>, // weak: a child only points back
}Example
use std::rc::{Rc, Weak}; use std::cell::RefCell;
struct Node { value: i32, parent: RefCell<Weak<Node>> }
fn main() {
let leaf = Rc::new(Node { value: 3, parent: RefCell::new(Weak::new()) });
let branch = Rc::new(Node { value: 5, parent: RefCell::new(Weak::new()) });
*leaf.parent.borrow_mut() = Rc::downgrade(&branch);
println!("leaf parent value = {:?}", leaf.parent.borrow().upgrade().map(|p| p.value));
}Key points
- A memory leak in Rust is memory-safe: the memory is wasted, never misused.
- Leaks are allowed.
- Dangling pointers are not.
- Cycles need a rewirable link to form: watch for RefCell<Rc<T>> combinations, where a link can be pointed back after creation
- If the honest answer is no, it should be a Weak.
- A cycle is a ring where Rc values keep each other's counts above zero.
In this example, Node is a tree node with a value and a parent pointer. The parent pointer is a RefCell<Weak<Node>> — a weak pointer that can be filled in after creation. leaf's parent is set to a downgrade of branch, meaning leaf can find branch but does not keep it alive. Printing goes through upgrade(), which returns an Option, and .map takes the value out of it: Some(5). If branch had been dropped first, upgrade() would give None instead of a dangling pointer.
Note
Ask of each pointer: "does this keep the target alive?"
Try it Yourself
Edit the code, then press Run.
Running is simulated · Rust Playground
Exercise
How do Weak references prevent cycles?