Skip to content

← Smart Pointers and Interior Mutability step 18 of 26

Hard Primitives

`Rc::new_cyclic`: self-reference in one step

Build entries that each hold a Weak handle back to themselves.

pub fn registry(names: Vec<String>) -> Vec<(String, bool, usize)>
pub fn cyclic_facts(names: Vec<String>) -> (Vec<bool>, Vec<String>)

An Entry holds its name, a me: Weak<Entry> pointing at itself, a seen_in_closure: bool recording whether upgrade() worked during construction, and a handle to a shared drop log.

registry returns, per name in order: (name, whether me.upgrade() succeeds now, the entry's strong count).

cyclic_facts returns (the seen_in_closure flags, the names in the order their destructors ran once the whole registry was released).

Read this before you write the count: a live Rc returned by upgrade() is a strong reference, and the temporary it lives in survives to the end of the statement. Measure Rc::strong_count before you upgrade, or you will report one more than you meant to.

The chicken and the egg

You want a value that contains a handle to itself. To make the handle you need the Rc. To make the Rc you need the value. Neither can go first.

The way most people solve it — and the starter is written this way on purpose — is to build it with a placeholder and patch it up afterwards:

let entry = Rc::new(Entry { name, me: Weak::new(), … });
entry.me = Rc::downgrade(&entry);
error[E0594]: cannot assign to data in an `Rc`
  |     entry.me = Rc::downgrade(&entry);
  |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ cannot assign
  = help: trait `DerefMut` is required to modify through a dereference,
          but it is not implemented for `Rc<Entry>`

Of course. Rc hands out &T, and it always will. The workaround people reach for next is to make the field RefCell<Weak<Entry>> — which is exactly what the previous problem’s tree had to do, for exactly this reason. It works, and it costs you three things: a RefCell you never needed, a field that is optional in the type but mandatory in the invariant, and a second construction pass you can forget to run.

Rc::new_cyclic

Rc::new_cyclic(|me: &Weak<Entry>| Entry {
    name,
    me: me.clone(),
    …
})

The closure is handed a Weak to the allocation before the value is written into it, and returns the value. One allocation, one step, and the field can be a plain Weak<Entry> with no Option and no RefCell — so “I forgot to set the back-pointer” stops being a possible bug, because the type will not let you construct an entry without one.

The trap that is worth knowing

Inside the closure, calling me.upgrade() returns None.

It has to. The allocation exists, and the weak counter is live — that is how you are holding a Weak at all — but the value has not been written yet, so there is nothing to hand out a strong reference to. Upgrading would produce an Rc to uninitialised memory.

This is documented, and it still catches people, because the natural reading of “here is a handle to the thing” is that the thing is there. seen_in_closure in this problem is false for every entry, always. After construction returns, the same weak handle upgrades happily.

So: inside the closure, use the Weak — do not dereference it. Store it, clone it, hand it to a child. Do not try to read through it.

What it does not solve

new_cyclic builds self-reference: one allocation whose value refers back to itself. It cannot build mutual cycles — two nodes each pointing at the other — because the second node does not exist when the first closure runs. For those you are back to RefCell<Weak<T>> and a patch-up pass, and you should ask hard whether the mutual link needs to be there at all.

And it does not leak

Note what the drop log says: every entry’s destructor runs. A self-reference through Weak is not a cycle in the strong graph, so the counts still reach zero. Had the field been me: RefCell<Option<Rc<Entry>>> — a self-reference through a strong handle — nothing would ever be dropped and the log would come back empty. That is the same lesson as the leak problem, at the smallest possible scale: one node, pointing at itself.

Verified behaviour to expect: after Rc::new_cyclic(|w| SelfRef { me: w.clone() }), the strong count is 1 and the weak count is 1.

Loading visualization…