Advanced Topics
Weak References
A Story
Suppose you are going to university. You don't feel like being extorted to live in the dorms, so you decide to get an apartment with one of your friends. You and your new roommate both like rice, so he brings his rice cooker with him. He gives you permission to use his rice cooker whenever you want. But it's not joint ownership - you can make rice with it, clean it, cuddle with it next to the fire while watching a sappy movie - but it's not yours, it's his. If he moves out and takes it with him, or if he wants to take it out to dinner or on a cruise, that's his prerogative, since it's his.
Fast forward a few years, and you've both just graduated from school. Now you've both got job offers in different cities, so you move out. The rice cooker of course goes with your roommate, because it's his. You're sitting in your new apartment, and you want some rice, but what do you do? You could use a pot, or you could go buy a new rice cooker, or you could curl up in a fetal position and cry yourself to sleep. I mean, you and that rice cooker had something special.
What does this tale of lost love have to do with anything? Well it points out the difference between ownership and access. Your roommate owns that rice cooker, and wherever he goes, it goes too. All you have is temporary access to it. You can tell whether or not it's there, and if it's not there, you have options as to what you can do.
Let's, uh..
Get Back to the Point
MiniD is garbage-collected, which means that removal of dead objects is handled for you automatically. As long as you hold at least one reference to an object, it will be kept alive. When an object no longer has any references to it, it becomes garbage and is collected.
Consider the following memory graph.
The globals and stack form the root of any memory graph. Any objects directly or indirectly accessible from the roots are still in use and therefore will not be collected. In this figure, 'a' and 'c' are directly accessible from the roots, and 'b', 'd', and 'e' are indirectly accessible.
When we remove all references to 'c' from the roots, what happens?
Now 'c' is not accessible from the roots, directly or indirectly, so it becomes garbage (indicated by red). Furthermore, since 'd' and 'e' are only referenced by garbage, they too must transitively be garbage. Now the GC can free the memory associated with those three objects.
Now let's see how a weak reference works.
Here we have two references to 'a'; a "strong" (normal) reference from the stack, and a weak reference from the globals (colored blue). Let's get rid of the reference from the stack:
Here's where it gets interesting. 'a', 'b', and 'c' are no longer accessible from the roots through any strong reference, so they must be garbage (again indicated by red). However, we still have the weak reference to 'a' from the globals! Now the GC frees those objects:
And now the weak reference points to.. well, nothing. It has "become null", which means that the object that it was referencing has been collected, so it no longer refers to any object. This is the behavior that makes weak references useful. We can use a weak reference to keep tabs on an object - to see if and when it has been collected.
And this is what the rice cooker story means. "Strong" or "normal" references correspond to ownership - your roommate has a strong reference to his rice cooker. But weak references correspond to access - you have a weak reference to your roommate's rice cooker. You can access it while it's around, but when it leaves, you know that, and you can do something else.
When you create a weak reference to an object, you will always get the same weak reference object. That is, there is only ever one weak reference object for each referenced object. For all values x, "weakref(x) is weakref(x)" holds.
Weak references are themselves objects, and like other objects, will only be collected once they are no longer accessible through any chain of strong references from the roots. However, when weak references are used as the keys or values of tables, something interesting happens. If a weak reference becomes null, any key-value pairs that use that weak reference as the key, value, or both are removed from all tables in existence. This means that you can never get a weak reference that points to nothing out of a table.
Uses for Weak References
The behavior of weak references lends itself particularly well to a few tasks. Most of these can be seen as a matter of association. This means that you can use weak references to associate one thing with something else externally, so that the association does not actually affect the lifetimes of the objects that it's associating. Sometimes the association can be from one object to itself, which is sort of a degenerate case - in that case, you're just using weak references to keep track of some set of objects without affecting their status as garbage or not-garbage.
One use of weak references is associating some set of attributes (meta-information) with an object. You keep a table which maps from weak references of the object to the metadata, like so:
{
// local, so it's private to these functions
local attrTab = {}
function setAttrs(o, data)
attrTab[weakref(o)] = data
function getAttrs(o) = attrTab[weakref(o)]
function debugAttrs()
{
write$ "attrTab = "
dumpVal$ attrTab
}
}
function foo() writeln("I'm foo!")
setAttrs$ foo, "hello!"
writeln(getAttrs(foo)) // prints "hello!"
debugAttrs() // we see a mapping from weakref(foo) to "hello!"
foo = null
collectGarbage() // bye bye, foo
debugAttrs() // the table is now empty - no need to keep attributes of an object that doesn't exist
In fact, this is precisely how the attrs, hasAttributes, and attributesOf functions in the base library work.
Another common use is something known as "signals and slots." This comes up a lot in GUI programming. Often, you'll have some object which can generate some events, like a button, and you'll want to hook up some event handlers to that object. Each event is called a "signal", and the event handlers are hooked up into "slots". Whenever the event-generating object generates an event, all the "listeners" will be informed, and they can respond to it in whatever way they like. However, the object does not own the listeners. It just has access to them. So we use weak references. This way, the lifetimes of the listeners are not controlled by the event-generating object. If the listener is collected, the event-generator just stops sending it events.
Implementing this in MiniD is trivial:
class Signal
{
this()
:listeners = {}
function add(f: function)
:listeners[weakref(f)] = true
function opCall(vararg)
foreach(f, _; :listeners)
deref(f)(vararg)
}
class Button
{
this()
:onClick = Signal()
function update()
:onClick(math.rand(800), math.rand(600)) // faking some coordinates :P
}
function foo(x, y)
writefln("mouse click at {}, {}", x, y)
local b = Button()
b.onClick.add$ foo // hook up a listener
b.update() // prints something
b.update() // prints something else
foo = null
collectGarbage() // there it goes
b.update() // prints nothing, since the listener was collected
Other uses include keeping lists of objects of a certain kind which are currently in use (like resources in a game), caching, and memoization. Weak references are cool; keep them in mind.
