Closures power callbacks, iterators, memoisation and simple objects throughout Lua code.
Lua has lexical scoping: a function can read and write the local variables of the blocks that enclose it, even after those blocks have finished. A captured variable is called an upvalue.
Each time a function expression is evaluated, a new closure is created with its own references to the captured variables. Two closures created in the same call share the same upvalue, so they see each other's updates.
This gives you private state cheaply. A counter factory returns a function that increments a local; nobody outside can touch the count. Memoisation is a closure over a cache table. Callbacks passed to an event system capture the context they need.
Loops create a fresh local for each iteration: closures created inside for i = 1, 3 each capture their own i, unlike var in old JavaScript.
Scope rules worth remembering:
- A
localis visible from the statement after its declaration to the end of its block. local function fmakesfvisible inside its own body, so it can recurse;local f = functiondoes not.do ... endcreates a block purely to limit scope.
Accessing upvalues is slightly slower than locals but much faster than globals, which is why performance-sensitive code often caches globals in locals (local insert = table.insert).
Example
local function counter()
local n = 0
return function() n = n + 1; return n end,
function() return n end
end
local inc, get = counter()
inc(); inc()
print(get()) --> 2 (shared upvalue)
local fns = {}
for i = 1, 3 do fns[i] = function() return i end end
print(fns[1](), fns[3]()) --> 1 3 (fresh i per iteration)
local function memoize(f)
local cache = {}
return function(x)
local v = cache[x]
if v == nil then v = f(x); cache[x] = v end
return v
end
end
local slow_sq = memoize(function(x) print("computing", x); return x * x end)
print(slow_sq(4)) --> computing 4 / 16
print(slow_sq(4)) --> 16 (cached)Remember
- A closure captures enclosing locals (upvalues) by reference
- Closures created in one call share the same upvalues
- Each loop iteration gets a fresh loop variable
Watch out for
- Using
local f = function() ... f() ... endfor recursion: inside the bodyfis still the global (nil).