Skip to content
Closures and Upvalues
step 1/5

Reading — step 1 of 5

Learn

~1 min readClosures and Tables

Lua closures capture upvalues by reference, not by copy. Multiple closures can share state.

local function makeCounter()
    local count = 0
    return function()
        count = count + 1
        return count
    end
end

local c1 = makeCounter()
local c2 = makeCounter()
print(c1())   -- 1
print(c1())   -- 2
print(c2())   -- 1 — different instance
print(c1())   -- 3

Each call to makeCounter() creates a fresh count upvalue. Each returned closure has its own.

Sharing upvalues between multiple closures:

local function makeAccount(initial)
    local balance = initial
    return {
        deposit  = function(n) balance = balance + n end,
        withdraw = function(n) balance = balance - n end,
        balance  = function() return balance end
    }
end

local a = makeAccount(100)
a.deposit(50)
a.withdraw(20)
print(a.balance())   -- 130

The three closures share the same balance upvalue. This is how you do private state in Lua — encapsulation without classes.

Closures + tables = OO without metatables:

local function newPerson(name)
    return {
        getName = function() return name end,
        setName = function(n) name = n end
    }
end

The name field isn't on the table — it's an upvalue that the methods can access. External code can't see it without going through the methods.

debug.getupvalue / debug.setupvalue for introspection (and breaking encapsulation, naturally):

local name, value = debug.getupvalue(account.balance, 1)
print(name, value)   -- "balance", 130

The debug library lets you inspect closures' captured state — useful for debugging, dangerous for security.

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…