Ludicrous Software

Using `do/end` for Disposable Code

This is a quick tip about how to take advantage of scope in Lua to keep memory consumption down. Typically, any application that you write is going to have some initialization code. By this, I mean code that is going to run once and only once. It may set up some initial values, do a bunch of calculations to configure your app for the specific device it’s on, and so on.

Even though this code may only run once, it might be very involved. Because you value clean, readable code, you might end up using a lot of local variables, even though you won’t need to reference these values again. The problem is, if you just stick this code inside your main.lua file (or a module), these variables will persist, taking up memory when you no longer need them. You have a couple of options to deal with this:

  • you could nil out all these variables, but that involves writing more code than necessary. I’ve done this before, and I’d like to avoid the extra work if I could. Plus, I think it’s somewhat inelegant to manually nil out a bunch of variables to make them eligible for garbage collection.
  • you could wrap all the initialization code inside a function. This is a little more elegant, since any locals within the scope of the function will get cleaned up after the function has completed. However, the function itself is still lying around (since that’s the whole point of a function), so you haven’t cleaned up after yourself completely. It’s like when you sweep debris onto a dustpan and you get that little line of dirt along the front edge of the pan. I hate that.

There’s one more option, which I think is more elegant than these two: wrap your initialization code inside a do/end block. Unlike code inside a function, code inside do/end is executed immediately. But the real beauty of do/end is that it has its own scope, just like a function, so any local variables inside the block can be garbage collected after the code block has finished execution. To compare it to a function, consider this simple code:

1
2
3
4
5
6
local function init()
  local a = "foo"
  print(a)
end
init()
print(a)

This is pretty standard Lua function scope. The first line of output will be “foo”, because init() is called first and references the local variable a within the scope of the function, and the second line of output is nil because a is within the scope of init(). In comparison, here’s similar code without the function call:

1
2
3
4
5
do
  local a = "foo"
  print(a)
end
print(a)

In this case, the code inside do/end is executed immediately - no function call required and no function hanging around after it’s no longer needed. And even though the code is executed immediately, it still has its own scope. The first line of output will be “foo”, and the second line will be nil, just as before, because a exists only within the scope of the do/end block.

Now, you may think that this degree of concern over memory consumption is unnecessary, but I hate that little line of dust in front of the dustpan. Plus I like the fact that this clearly communicates that the initialization code is completely and utterly disposable