🎓 A1Learner
🔍

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 cycle

Downgrade 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?