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
docshas strong count 2 (thedocsvariable andhome's children list) and weak count 1 (taxespoints up at it).pathwalks upward:upgrade()turns the Weak parent link into a usableRcwhen the parent exists.drop(home)drops the only strong owner ofhome, so it's freed even thoughdocsstill has a Weak pointing at it. Afterwardsupgrade()returnsNone. No cycle, no leak.
Common mistakes
Rcin both directions (parent to child and child to parent): a silent leak. If aDropmessage never prints, look for a cycle.- Forgetting that
upgrade()can fail. It returns anOption; decide what "the parent is gone" means for your code instead of calling.unwrap()everywhere. - Making the owning direction Weak: if the parent only had Weak pointers to its children, the children would be dropped immediately.
Your task
Build an org chart. Each Employee owns its direct reports (Rc) and points up at its manager (Weak). Write:
hire(manager, report): sets the report's manager link and adds the report to the manager'sreports.manager_name(e) -> Option<String>: the manager's name, orNoneat the top.chain_of_command(e) -> Vec<String>: the names frome's manager upward to the top, for example["Bo", "Ana"].
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