Functions, closures and multiple returns
Treat functions as values, use varargs and multiple returns safely, and build closures and stateful callbacks with upvalues.
Functions as values
-- three spellings of the same thing
local function add(a, b) return a + b end
local sub = function(a, b) return a - b end
local tbl = { mul = function(a, b) return a * b end }
-- functions are first-class: store, pass and return them
local ops = { add = add, sub = sub, mul = tbl.mul }
local function apply(op, a, b) return op(a, b) end
print(apply(ops.add, 2, 3)) --> 5
-- higher-order: return a new function
local function twice(f)
return function(x) return f(f(x)) end
end
local add4 = twice(function(x) return x + 2 end)
print(add4(10)) --> 14
-- a table can carry the function and its context
local counter = { n = 0 }
function counter:incr(step) -- sugar for counter.incr(self, step)
self.n = self.n + (step or 1)
return self.n
end
counter:incr()
counter:incr(5)
print(counter.n) --> 6- The colon is pure syntax:
obj:method(a)is exactlyobj.method(obj, a). Mixing the two on the same function is the most common beginner error. - A function defined with
local function fcan call itself recursively.local f = function() ... f() ... endcannot, becausefis still nil inside the body. - Look up a method once outside a hot loop:
local insert = table.insertthen callinsert(t, v). It avoids a global lookup plus a hash lookup on every call. - Lua does not have overloading or default parameters in the type sense. Use
orfor defaults and check types explicitly when the argument matters.
Multiple returns and varargs
local function divmod(a, b)
return a // b, a % b -- two values
end
local q, r = divmod(17, 5)
print(q, r) --> 3 2
-- the multiple-value rule: only the LAST expression in a list expands
local function two() return 1, 2 end
print(two()) --> 1 2
print((two())) --> 1 parentheses truncate to one value
print(two(), 3) --> 1 2 3
print(3, two()) --> 3 1 2
local t = { two() } --> t = {1, 2}
local t2 = { two(), two() } --> t2 = {1, 1, 2}
-- varargs
local function sum(...)
local total = 0
for _, v in ipairs({...}) do total = total + v end -- {...} truncates to a table
return total
end
print(sum(1, 2, 3, 4)) --> 10
-- table.pack keeps the count, including trailing nils
local function count(...)
local packed = table.pack(...)
return packed.n
end
print(count(1, nil, 3)) --> 3
-- select is the safe way to inspect varargs without building a table
local function head(...) return (select(1, ...)) end
local function rest(...) return select(2, ...) end
print(select('#', 1, nil, 3)) --> 3| Expression | Result | Why |
|---|---|---|
f() as the last argument | All return values | The only position that expands |
(f()) | The first value only | Parentheses force a single value |
{f()} | A table of all values | Except that a trailing nil is dropped |
table.pack(f()) | All values plus n | Preserves trailing nils |
f(), g() | First of f, all of g | Only the last expands |
A trailing nil in a multiple return disappears when the values land in a table, because the table constructor stops at the first nil hole. When that matters, return an explicit count or use table.pack.
Closures, upvalues and tail calls
-- a closure keeps its upvalues alive after the enclosing function returns
local function makeCounter(start)
local n = start or 0
return {
incr = function() n = n + 1; return n end,
get = function() return n end,
}
end
local c = makeCounter(10)
c.incr()
print(c.get()) --> 11
-- each call to the factory creates fresh upvalues
local a, b = makeCounter(0), makeCounter(100)
a.incr()
print(a.get(), b.get()) --> 1 100
-- the loop-variable trap: Lua 5.1 shared one variable per loop in some idioms
local fns = {}
for i = 1, 3 do
fns[i] = function() return i end -- Lua 5.4: each iteration has its own i
end
print(fns[1](), fns[2](), fns[3]()) --> 1 2 3
-- in 5.1, capture a copy to be safe
local safe = {}
for i = 1, 3 do
local copy = i
safe[i] = function() return copy end
end
-- tail calls do not grow the stack: make the recursive call the last action
local function loop(n, acc)
if n == 0 then return acc end
return loop(n - 1, acc + n) -- a real tail call
end
print(loop(1000000, 0)) --> 500000500000
local function notTail(n)
if n == 0 then return 0 end
return 1 + notTail(n - 1) -- NOT a tail call: a stack frame per step
end💡
A proper tail call reuses the current frame, so a tail-recursive function can loop forever without a stack overflow.
return f(x) is a tail call; return (f(x)) and return 1 + f(x) are not, because work remains after the call returns.FAQ
How do I create a default argument?
Use
or: local function f(opts) opts = opts or {} ... end. Remember that false and nil are both falsey, so a boolean option that defaults to true needs an explicit if opts.flag == nil then opts.flag = true end.What is the maximum number of return values?
There is no fixed limit in the language, but practical limits come from the C API and from
unpack: passing a very large number of arguments can overflow the stack. Split the data into a table beyond a few dozen values.Related
Lua syntax and types Iterators: pairs, ipairs and generic for
Last refreshed 2026-09-18.