Closures in Lua
A closure is a function that captures and remembers variables from its surrounding scope, even after that scope has finished executing. Closures are one of Lua's most powerful features, enabling elegant solutions to complex problems. They're essential for creating private variables, factory functions, callbacks, and functional programming patterns. Let's explore how closures work and how to use them effectively!
What is a Closure?
A closure is created when a function accesses variables from an outer scope:
local function createCounter()
local count = 0 -- This variable is "captured"
return function()
count = count + 1
return count
end
end
local counter = createCounter()
print(counter()) -- 1
print(counter()) -- 2
print(counter()) -- 3
count
variable. Even though createCounter() has finished executing, the
returned function still has access to count!
Click Run to execute your code
Lexical Scoping
Lua uses lexical scoping, meaning functions can access variables from their enclosing scopes:
local x = 10
local function outer()
local y = 20
local function inner()
local z = 30
print(x, y, z) -- Can access all three!
end
inner()
end
outer() -- 10 20 30
Private Variables
Closures enable true private variables in Lua:
local function createBankAccount(initialBalance)
local balance = initialBalance -- Private!
return {
deposit = function(amount)
balance = balance + amount
return balance
end,
withdraw = function(amount)
if amount > balance then
return nil, "Insufficient funds"
end
balance = balance - amount
return balance
end,
getBalance = function()
return balance
end
}
end
local account = createBankAccount(1000)
print(account.getBalance()) -- 1000
account.deposit(500)
print(account.getBalance()) -- 1500
-- print(balance) -- Error: balance is not accessible!
Click Run to execute your code
Factory Functions
Closures are perfect for creating factory functions that generate customized functions:
local function createMultiplier(factor)
return function(x)
return x * factor
end
end
local double = createMultiplier(2)
local triple = createMultiplier(3)
local quadruple = createMultiplier(4)
print(double(5)) -- 10
print(triple(5)) -- 15
print(quadruple(5)) -- 20
More Factory Examples
-- Greeting factory
local function createGreeter(greeting)
return function(name)
return greeting .. ", " .. name .. "!"
end
end
local sayHello = createGreeter("Hello")
local sayHi = createGreeter("Hi")
print(sayHello("Alice")) -- Hello, Alice!
print(sayHi("Bob")) -- Hi, Bob!
-- Validator factory
local function createValidator(min, max)
return function(value)
return value >= min and value <= max
end
end
local isValidAge = createValidator(0, 120)
local isValidScore = createValidator(0, 100)
print(isValidAge(25)) -- true
print(isValidScore(150)) -- false
Click Run to execute your code
Callbacks and Event Handlers
Closures are commonly used for callbacks that need to remember context:
local function createButton(label)
local clickCount = 0
return {
onClick = function()
clickCount = clickCount + 1
print(label .. " clicked " .. clickCount .. " times")
end
}
end
local button1 = createButton("Submit")
local button2 = createButton("Cancel")
button1.onClick() -- Submit clicked 1 times
button1.onClick() -- Submit clicked 2 times
button2.onClick() -- Cancel clicked 1 times
Custom Iterators
Closures enable powerful custom iterators:
local function range(from, to, step)
step = step or 1
local current = from - step
return function()
current = current + step
if current <= to then
return current
end
end
end
-- Use in for loop
for i in range(1, 10, 2) do
print(i) -- 1, 3, 5, 7, 9
end
-- Fibonacci iterator
local function fibonacci()
local a, b = 0, 1
return function()
a, b = b, a + b
return a
end
end
local fib = fibonacci()
for i = 1, 10 do
print(fib()) -- 1, 1, 2, 3, 5, 8, 13, 21, 34, 55
end
Click Run to execute your code
Memoization Pattern
Use closures to cache expensive function results:
local function memoize(func)
local cache = {}
return function(x)
if cache[x] == nil then
cache[x] = func(x)
end
return cache[x]
end
end
-- Expensive fibonacci
local function fib(n)
if n <= 1 then return n end
return fib(n - 1) + fib(n - 2)
end
local fastFib = memoize(fib)
print(fastFib(30)) -- Fast!
print(fastFib(30)) -- Even faster (cached)!
Common Closure Patterns
1. Configuration Object
local function createConfig()
local settings = {}
return {
set = function(key, value)
settings[key] = value
end,
get = function(key)
return settings[key]
end,
getAll = function()
local copy = {}
for k, v in pairs(settings) do
copy[k] = v
end
return copy
end
}
end
2. State Machine
local function createStateMachine()
local state = "idle"
return {
getState = function()
return state
end,
transition = function(newState)
print("Transitioning from " .. state .. " to " .. newState)
state = newState
end
}
end
3. Partial Application
local function partial(func, ...)
local args = {...}
return function(...)
local allArgs = {}
for i, v in ipairs(args) do
table.insert(allArgs, v)
end
for i, v in ipairs({...}) do
table.insert(allArgs, v)
end
return func(table.unpack(allArgs))
end
end
local function add(a, b, c)
return a + b + c
end
local add5 = partial(add, 5)
print(add5(3, 2)) -- 10
Click Run to execute your code
Practice Exercise
Try these closure challenges:
Click Run to execute your code
Summary
In this lesson, you learned:
- What closures are and how they work
- Lexical scoping and upvalues
- Creating private variables with closures
- Factory functions for generating customized functions
- Using closures for callbacks and event handlers
- Custom iterators with closures
- Memoization pattern for optimization
- Common closure patterns (config, state machine, partial application)
What's Next?
You've mastered closuresβone of Lua's most powerful features! Next, we'll explore advanced function techniques including higher-order functions, function composition, currying, and functional programming patterns. Let's continue! π
Enjoying these tutorials?