RustArena

Step 7 of 7

Weak<T>: parent links without leaks

Reference counting has one weak spot: cycles. If a holds an Rc to b and b holds an Rc to a, each keeps the other's count above zero. When your variables go away, the counts drop to 1, never 0, and neither value is ever dropped. That's a memory leak. Rust's safety guarantees don't cover leaks (a leak can't corrupt memory, it just wastes it), so the compiler won't warn you:

use std::cell::RefCell;
use std::rc::Rc;

struct Room {
    name: String,
    next: RefCell<Option<Rc<Room>>>,
}

impl Drop for Room {
    fn drop(&mut self) {
        println!("dropping {}", self.name);
    }
}

fn main() {
    let a = Rc::new(Room { name: String::from("a"), next: RefCell::new(None) });
    let b = Rc::new(Room { name: String::from("b"), next: RefCell::new(Some(Rc::clone(&a))) });
    *a.next.borrow_mut() = Some(Rc::clone(&b)); // a -> b -> a: a cycle
    println!("a count = {}, b count = {}", Rc::strong_count(&a), Rc::strong_count(&b));
    println!("end of main");
}
a count = 2, b count = 2
end of main

No dropping lines: both rooms leaked.

Weak: a pointer that doesn't own

Rc::downgrade(&rc) creates a Weak<T>: a pointer that does not keep the value alive. It adds to the weak count, not the strong count. Because the value might be gone, you can't use a Weak directly: weak.upgrade() returns Option<Rc<T>>, Some if the value is still alive and None if it has been dropped. Weak::new() makes an empty Weak that never upgrades, handy for "no parent yet".

The rule of thumb: owners point down with Rc, back-links point up with Weak. A parent owns its children; a child only refers to its parent.

use std::cell::RefCell;
use std::rc::{Rc, Weak};

struct Folder {
    name: String,
    parent: RefCell<Weak<Folder>>,
    children: RefCell<Vec<Rc<Folder>>>,
}

fn folder(name: &str) -> Rc<Folder> {
    Rc::new(Folder {
        name: name.to_string(),
        parent: RefCell::new(Weak::new()),
        children: RefCell::new(Vec::new()),
    })
}

fn add_child(parent: &Rc<Folder>, child: &Rc<Folder>) {
    *child.parent.borrow_mut() = Rc::downgrade(parent);
    parent.children.borrow_mut().push(Rc::clone(child));
}

fn path(f: &Rc<Folder>) -> String {
    match f.parent.borrow().upgrade() {
        Some(p) => format!("{}/{}", path(&p), f.name),
        None => f.name.clone(),
    }
}

fn main() {
    let home = folder("home");
    let docs = folder("docs");
    let taxes = folder("taxes");
    add_child(&home, &docs);
    add_child(&docs, &taxes);
    println!("{}", path(&taxes));
    println!("docs: strong = {}, weak = {}", Rc::strong_count(&docs), Rc::weak_count(&docs));

    drop(home);
    println!("docs still has a parent: {}", docs.parent.borrow().upgrade().is_some());
}
home/docs/taxes
docs: strong = 2, weak = 1
docs still has a parent: false

Common mistakes

Your task

Build an org chart. Each Employee owns its direct reports (Rc) and points up at its manager (Weak). Write:

Challenge 2

Fill the blanks: give the comment a non-owning link to its thread, then follow it. The program prints:

reply to: Release day
thread owners: 1
after drop: thread gone

Challenge 3

This playlist program leaks: each Track keeps a strong Rc back to its Playlist, and the playlist owns its tracks, so nothing is ever dropped and the Drop messages never print. Change the back-link to a Weak (and update the code that sets and reads it) so the program prints exactly:

Night Drive has 2 tracks
Neon is on Night Drive
end of main
dropping playlist Night Drive
dropping track Neon
dropping track Rain

Previous: Rc<RefCell<T>>: shared state you can change