Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Who cleans up?

I do! You never free anything. There’s no ERASE, no delete, no free. But it’s worth knowing how I decide something is finished with, because it tells you how long your things last.

Things last as long as something holds them

A list, a map or an object stays around for as long as anything is holding it: a variable, another list, a field of an object, a function that kept it. The very moment the last holder lets go, it’s gone, and its memory with it.

TYPE Monster
    name AS STRING
END TYPE

horde = []

FUNCTION spawn(name)
    LOCAL fresh = Monster({name: name})
    PUSH horde, fresh              // the horde holds it now
END FUNCTION                       // `fresh` lets go here; the horde hasn't

spawn("slime")
spawn("bat")
PRINT LEN(horde), horde(0).name

gone = DEQUEUE(horde)              // out of the horde, into `gone`
PRINT LEN(horde), gone.name
gone = NULL                        // the last holder lets go: the slime is freed
2 slime
1 slime

That’s the whole rule. To keep something, put it somewhere: a variable, a list. To let it go, take it out, or give the variable something else. Numbers, text and coordinates are simpler still: each variable has its own copy, so there’s nothing to think about for those.

A local variable lets go when its function finishes. That’s why a function can make a thousand little lists to do its work and none of them pile up.

FUNCTION busy()
    FOR i = 1 ... 1000
        LOCAL scrap = [i, i * 2, i * 3]     // made, used, and freed each pass
    END FOR
    RETURN "all tidy"
END FUNCTION

PRINT busy()
all tidy

Things that hold each other

Here’s the one tricky case, and I handle it too. Sometimes two things hold each other. The player’s target is the enemy, and the enemy’s target is the player. Even when you’ve let go of both, each still has a holder: the other one.

TYPE Fighter
    name AS STRING
    target AS ANY
END TYPE

FUNCTION duel()
    LOCAL ada = Fighter({name: "Ada"})
    LOCAL bug = Fighter({name: "Bug"})
    ada.target = bug
    bug.target = ada               // a ring: each holds the other
    RETURN ada.target.target.name
END FUNCTION                       // nobody outside can reach either one now

PRINT duel()
Ada

Counting holders can’t free those two, since neither count ever gets to nothing. So every so often I stop and look for rings like this: groups of things that are held only by each other, that no variable and no running function can get to any more. Those I take apart and free.

You don’t have to break the ring yourself, or avoid making one. A child that remembers its parent, a list whose items point back at it, a map of handlers that mention the map: go ahead.

What this means for you

  • Nothing to free, ever. Let go of things by not holding them.
  • Ordinary things are freed right away. Memory stays flat while you play; there’s no clean-up to wait for.
  • Rings are freed a little later, the next time I look. How often depends on how much you’re making.
  • I never take anything you can still reach. If a variable, a list or a running function leads to it, it’s safe — and so is everything it leads to.
  • If you want something gone, check who’s holding it. The usual culprit is a list you forgot it was in: every monster ever spawned, still sitting in horde long after it was beaten.

From Python: The same model as CPython: counted, with a collector for cycles. There’s no del and no gc module to poke.

From JavaScript: No need to wait for a collector for most things — a value is freed when its last holder goes. Rings take a moment longer.

From Lua: Like your collector, but most memory comes back straight away. No collectgarbage to call.

From BASIC: No ERASE and no REDIM to shrink things. Give the variable something else — NULL, or [] — and the old value is gone.

Crash-tan’s Tip: At the end of a level, give the big lists fresh empty ones: horde = []. Everything that was only in the old list goes with it, in one line.