“Functions are first-class citizens in Lua.”
Lua treats functions as values — they can be stored in variables, passed around, returned from other functions, and even created at runtime. This post explores the full scope of Lua’s function system, including declaration styles, scoping, closures, recursion, and more.
What is a Function in Lua?
A function is a reusable block of code that performs a task. It’s defined using the function keyword.
function greet(name)
print("Hello, " .. name .. "!")
end
greet("Alice") --> Hello, Alice!
Function Declaration Syntax
1. Global Function
function add(a, b)
return a + b
end
This is equivalent to:
add = function(a, b)
return a + b
end
2. Local Function
local function multiply(a, b)
return a * b
end
Using local avoids polluting the global namespace — highly recommended in modular code.
Anonymous Functions (Function Expressions)
You can create functions without names, store them in variables or pass them around.
local say = function(msg)
print("You said: " .. msg)
end
say("Lua is awesome!")
Also common for callbacks:
function handle(callback)
callback("event triggered")
end
handle(function(e) print("Event: " .. e) end)
Functions are First-Class Values
This means you can:
- Assign functions to variables
- Store them in tables
- Pass them as arguments
- Return them from other functions
Example:
local function make_adder(x)
return function(y)
return x + y
end
end
local add10 = make_adder(10)
print(add10(5)) --> 15
This is a closure (see below).
Certainly! Here’s the updated Recursion section with a safety disclaimer:
Recursion
A function can call itself. This is useful for problems that can be broken into smaller, similar subproblems (e.g., trees, factorials, file traversals).
function factorial(n)
if n == 0 then return 1 end
return n * factorial(n - 1)
end
print(factorial(5)) --> 120
Disclaimer: Writing Safe Recursive Code
Recursive functions must always have:
- A clear base case to prevent infinite recursion.
- Arguments that progress toward the base case.
- Reasonable depth, as Lua has limited call stack space.
If the recursion gets too deep (e.g., in complex tree or graph structures), you may hit a stack overflow. In such cases, consider rewriting the logic iteratively or using tail-call optimization if applicable.
Method Definition: The : Operator
Lua supports syntactic sugar for object-like method definitions:
local person = {}
function person:speak()
print("My name is " .. self.name)
end
person.name = "Bob"
person:speak() --> My name is Bob
Using : automatically passes self as the first argument.
Equivalent without sugar:
function person.speak(self)
print("My name is " .. self.name)
end
Closures and Upvalues
When a function accesses variables from its enclosing scope, it becomes a closure.
function counter()
local count = 0
return function()
count = count + 1
return count
end
end
local inc = counter()
print(inc()) --> 1
print(inc()) --> 2
Here, count is preserved between calls because the inner function closes over it.
Varargs: ... in Lua
Functions can accept a variable number of arguments using ....
function sum(...)
local total = 0
for _, v in ipairs({...}) do
total = total + v
end
return total
end
print(sum(1, 2, 3, 4)) --> 10
To pass ... to another function:
function log_args(...)
print("Arguments:", ...)
end
Best Practices
- Use
localfor function declarations unless global scope is needed. - Prefer
:when defining methods on tables or objects. - Use closures to encapsulate state cleanly.
- Avoid deep recursion in environments without tail-call optimization.
Common Patterns
Factory Function
function new_counter()
local count = 0
return {
increment = function()
count = count + 1
return count
end
}
end
local c = new_counter()
print(c.increment()) --> 1
Callback
function on_event(callback)
callback("hello")
end
on_event(function(msg) print(msg) end)
Calling Functions in Lua
In Lua, functions can be called in a few different syntactic ways depending on your use case. Here are all the valid ways to call functions.
Standard function call
print("Hello, world!")
math.max(10, 20)
Calling with a single string literal
If you’re passing only one string literal as an argument you can remove the parentheses:
print "Hello, no parentheses!"
Equivalent to:
print("Hello, no parentheses!")
Calling with a single table literal
If you’re passing a single table literal you can also remove the parentheses:
table.unpack { x = 10, y = 20 }
Equivalent to:
table.unpack({ x = 10, y = 20 })
Summary
| Concept | Description |
|---|---|
function keyword |
Declares a function |
| First-class values | Can store, pass, and return functions |
| Closures | Functions with remembered outer variables |
| Recursion | Functions that call themselves |
Method syntax : |
Syntactic sugar for object-like functions |
Varargs ... |
Accept any number of arguments |