Reference counting: local release rules and cyclic retention
Trace strong and weak ownership edges, identify cycles, and separate atomic handle management from safe mutation.
On this page
The question for today
What you’ll understand
- Update an ordinary strong-reference count across ownership changes.
- Explain why a closed strong cycle remains retained after external owners disappear.
- Use weak edges without assuming they guarantee that a target remains alive.
Reference counting distributes release decisions across ownership operations. Acquiring another strong owner increments a count; dropping one decrements it; reaching zero permits destruction under the implementation's rules. This makes many lifecycles easy to follow, but a count answers a local question. A group of objects can keep one another counted even after the application has lost every path that could use them.
Count owners, not every observed pointer
For a conventional reference-counted object, distinguish an owning strong handle from a temporary borrow or a non-owning observation. Only ownership operations specified by the API affect the count. Copying an Rc handle through its clone operation adds a strong owner without cloning the payload. Borrowing through that handle does not create another independently owning payload. The implementation also keeps metadata, and exact object layout and count optimizations vary. Treat a simple integer count as a teaching model rather than a universal description of every runtime.
An atomic reference count coordinates concurrent ownership updates; it does not make arbitrary fields inside the object safe for concurrent mutation. Rust's Arc and Rc differ in thread-related guarantees, while the referent's synchronization requirements remain separate. A decrement that destroys a value can run arbitrary cleanup code and release further objects. Design code so a destructor or callback cannot observe an inconsistent container state. In language runtimes with reentrant finalization behavior, releasing a reference can have effects well beyond subtracting an integer.
A cycle has incoming edges from inside itself
Draw two objects A and B, with A strongly owning B and B strongly owning A. Initially an external handle also owns A. Removing that external handle leaves one internal strong owner for each object. Neither count reaches zero, so plain reference counting cannot start their destruction. Their retention does not require another thread or a malfunctioning counter. It follows from the graph. A larger cycle can include callbacks, observer registrations, closures, and parent-child relationships that are less obvious than two explicit fields.
A weak edge describes a relationship without keeping the target payload alive. For example, a parent may strongly own children while each child weakly refers back to its parent. Access through a weak handle must attempt an upgrade and handle failure if the last strong owner has disappeared. Depending on the API, weak handles can retain a control block after payload destruction. They therefore avoid a particular ownership cycle without promising zero metadata retention. Alternatively, explicit lifecycle code can sever edges, and some runtimes combine reference counting with cycle detection.
Check graph shape before tuning increments
When investigating retained objects, inspect ownership paths and their intended lifecycles before optimizing atomic increments. A permanent registry holding a strong handle is different from an isolated cycle, and both differ from a legitimately long-running request. Capture the path from a root or identify the closed strongly connected component. If the design intentionally keeps a shared cache, define eviction and size limits. Memory safety permits objects to remain live; it does not promise that every reachable or internally counted object is still useful to the application.
The probability lab offers a limited completion model. Suppose three independent external owners each release by a deadline with probability one half, and there are no internal strong edges. Set n=3 and p=0.5: the bar for three completions has probability one eighth. That is the probability all three have released under these assumptions. Raising p increases it. The lab does not manipulate reference counts, discover cycles, or model correlated owners. Add a strong A–B cycle on paper and explain why the same completion calculation no longer proves either payload can be destroyed.
Work it through
Break one ownership edge
A and B each hold 128 bytes of payload. A strongly owns B, B strongly owns A, and one external handle owns A. Ignore metadata and other allocations.
Initially A has count two: one external owner and B's owner. B has count one from A. Releasing the external handle leaves counts one and one.
The closed cycle retains both payloads, totaling 256 bytes. No zero-count transition occurs under plain reference counting.
Redesign B's edge to A as weak while A strongly owns B. When the last external A owner disappears, A can be destroyed and release B. With no other strong owners, B can then be destroyed; any weak-control metadata follows the API's separate rules.
Changing one edge's ownership meaning removes this particular cycle. The weak edge must tolerate the target no longer being available.
The living notebook
Explore the model
Interactive lab · probability
When have all external owners finished?
Set n=3 and p=0.5 for independent owners' deadline-completion events. The probability bar at three completions is 0.125; increasing p raises it. The lab assumes no internal ownership cycle and does not implement reference counting or cycle detection.
- Mean · np
- 5
- Variance · np(1−p)
- 2.5
- Total probability
- 1
These are exact binomial probabilities for independent trials with one fixed success probability. Playback tours the distribution; it does not resample outcomes. Parameter changes pause playback and recompute the model at the current elapsed time. Reset restores both parameters and returns time to zero. Playback starts paused. Adjust either parameter, then choose Play or Step.
Look forAll external owners finishing only implies release when no other strong ownership edges remain.
Pause & reason
Check your understanding
Your turn
Try it yourself
A parent strongly owns two children. Each child weakly refers to the parent. There are no other strong owners. What happens after the sole external parent owner is released, and what must a surviving weak observer handle?
Take a moment to make a prediction or work it out on paper. Open a hint when you need one.
Give me a hint
Trace strong edges for payload retention; treat weak upgrade as fallible.
Compare with the solution
The parent loses its final strong owner and can release its child handles during destruction. Each child then loses its only strong owner and can also be destroyed. A surviving weak observer must handle a failed upgrade; its existence may retain control metadata but does not keep the parent payload alive under this ownership contract.
Keep the idea
Take it with you
- Strong ownership operations, rather than every pointer observation, determine ordinary reference counts.
- Closed strong cycles retain objects without any counter bug.
- Weak relationships avoid ownership retention but require handling an absent target.
Read further
Original sources and deeper explanations to follow at your own pace.
Optional progress, saved in this browser when available. Revisit any lesson at any time.
Lesson not marked complete.