Step 1 of 5 · Reading · ~3 min
Learn
Functions
Closures
A Lua function does not only remember its parameters. It remembers the local variables that were in scope where it was written, and it keeps them alive for as long as the function itself lives. Those captured variables are called upvalues, and a function together with its upvalues is a closure.
Every function in Lua is a closure. There is no second kind.
The classic shape
local function makeCounter()
local n = 0
return function()
n = n + 1
return n
end
end
local tick = makeCounter()
print(tick())
print(tick())
print(tick())
--> 1
--> 2
--> 3
makeCounter returned long ago and its stack frame is gone — but n is not. The inner function captured it, so n lives on as private state that nothing outside the closure can name, read, or corrupt.
Each call captures its own
Call the maker twice and you get two independent counters, because each call creates a fresh n.
local a = makeCounter()
local b = makeCounter()
print(a())
print(a())
print(b())
--> 1
--> 2
--> 1
Two closures can share one upvalue
When two inner functions are created in the same scope, they capture the same variable — not a copy each.
local function makeAccount(balance)
local function deposit(amount)
balance = balance + amount
end
local function get()
return balance
end
return deposit, get
end
local put, show = makeAccount(100)
put(50)
put(25)
print(show())
--> 175
balance is a parameter, and parameters are locals, so it gets captured like any other. This is how you build an object with genuinely private fields in a language that has no private keyword: the data is not hidden by a rule, it is unreachable because no name for it exists outside.
Are loop variables captured per iteration?
Languages disagree on this one, and the answer decides whether a loop full of closures is useful or useless.
local fns = {}
for i = 1, 3 do
fns[i] = function() return i end
end
print(fns[1]())
print(fns[3]())
--> 1
--> 3
Each pass of a for loop gets a fresh copy of the control variable, so every closure captured a different i. The same holds for any local declared inside the loop body.
✗ a local declared outside the loop is a single variable shared by every closure made inside it — occasionally what you want, usually the bug.
Your exercise
Make a Counter asks for counter(start): a function returning a function, where each call to the inner one yields the next integer, beginning at start.
Read "beginning at start" carefully, because that is exactly where the example above differs from what the exercise needs. makeCounter increments before returning, so with n starting at 0 its first result is 1. Copy that shape as-is and your first result becomes start + 1: the visible test passes start of 1 with 3 calls and expects 1 2 3, but you would print 2 3 4. Either grab the current value before incrementing, or seed the upvalue one below start.
The starter also supplies the driver loop and the print, so do not print from inside the closure. If you print in there and return nothing, the driver's own print receives no values and adds a blank line after each number: the output comes out 1, blank, 2, blank, 3, blank. Every number is correct and the run still fails — which is the hardest kind of failure to spot.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…