Lua Functions Guide — First-Class Functions, Closures, and Varargs
In this tutorial, you will learn about Lua Functions Guide. We cover key concepts, practical examples, and best practices to help you master this topic.
Lua functions are first-class values -- they can be assigned to variables, passed as arguments to other functions, returned as results, and captured in closures with lexical scoping.
What You'll Learn
- Defining and calling functions with multiple returns
- Using functions as first-class values
- Creating closures with captured variables
- Handling variable arguments with
...
Why It Matters
Functions are the primary means of abstraction in Lua. Without built-in classes, functions and closures are how you organize code, create callbacks, implement iterators, and build reusable abstractions. Durga Antivirus Pro uses Lua closures for threat detection rules that capture context from their environment.
Real-World Use
Love2D games use functions for draw and update callbacks. Redis Lua scripts use functions for atomic operations on the server. Nginx/OpenResty uses Lua closures for request handlers that capture configuration variables.
flowchart LR
A["Functions"] --> B["Definition"]
A --> C["Returns"]
A --> D["Closures"]
A --> E["Varargs"]
B --> F["Callbacks"]
D --> F
A:::current --> B
style A fill:#2563eb,stroke:#2563eb,color:#fff
style B fill:#dbeafe,stroke:#2563eb,color:#1e40af
style C fill:#dbeafe,stroke:#2563eb,color:#1e40af
style D fill:#dbeafe,stroke:#2563eb,color:#1e40af
style E fill:#f1f5f9,stroke:#94a3b8,color:#64748b
Defining Functions
-- Standard syntax
local function greet(name)
return "Hello, " .. name
end
-- Anonymous function assigned to variable
local greet = function(name)
return "Hello, " .. name
end
-- Calling
print(greet("Lua")) --> Hello, Lua
Syntax Details
The end keyword terminates every function. The return keyword is optional -- if omitted, the function returns nil.
local function add(a, b)
return a + b
end
local function log_message(msg)
print("[LOG] " .. msg)
-- implicit return nil
end
Multiple Return Values
Lua functions can return multiple values -- a feature that eliminates the need for tuple or wrapper objects.
local function divide(a, b)
if b == 0 then
return nil, "division by zero"
end
return a / b, nil
end
-- Capture all returns
local result, err = divide(10, 3)
print(result, err) --> 3.3333333333333 nil
local result2, err2 = divide(10, 0)
print(result2, err2) --> nil division by zero
-- Discard values with _
local ok, _ = divide(10, 2)
print(ok) --> 5
Common Built-in Multiple Returns
-- string.find
local start, finish = string.find("hello world", "world")
print(start, finish) --> 7 11
-- ipairs iterator
local iter, tbl, index = ipairs({10, 20, 30})
print(iter, type(tbl), index) --> function table 0
Functions as First-Class Values
-- Store in table
local operations = {
add = function(a, b) return a + b end,
sub = function(a, b) return a - b end,
mul = function(a, b) return a * b end
}
print(operations.add(5, 3)) --> 8
-- Pass as argument (higher-order function)
local function apply(func, value)
return func(value)
end
local double = function(x) return x * 2 end
print(apply(double, 21)) --> 42
-- Return function from function
local function make_counter()
local count = 0
return function()
count = count + 1
return count
end
end
local counter = make_counter()
print(counter()) --> 1
print(counter()) --> 2
print(counter()) --> 3
Closures
A closure is a function plus all the local variables it captures from its enclosing scope (called "upvalues").
local function make_adder(n)
-- n is captured by the returned function
return function(x)
return x + n
end
end
local add5 = make_adder(5)
local add10 = make_adder(10)
print(add5(20)) --> 25
print(add10(20)) --> 30
Practical Closure: Iterator with State
local function range_iterator(start, stop)
local i = start - 1
return function()
i = i + 1
if i <= stop then
return i
end
return nil
end
end
for i in range_iterator(1, 5) do
print(i)
end
Expected output:
1
2
3
4
5
Variable Arguments (Varargs)
Functions can accept any number of arguments using ....
local function sum(...)
local total = 0
for i = 1, select("#", ...) do
total = total + select(i, ...)
end
return total
end
print(sum(1, 2, 3)) --> 6
print(sum(10, 20, 30, 40)) --> 100
-- Using select for individual args
local function first_and_last(...)
local first = (...)
local last = select(select("#", ...), ...)
return first, last
end
local f, l = first_and_last(1, 2, 3, 4, 5)
print(f, l) --> 1 5
Varargs with Pack
In Lua 5.2+, use table.pack and table.unpack for flexible vararg handling.
local function process(...)
local args = table.pack(...)
print("Received " .. args.n .. " arguments")
for i = 1, args.n do
print(" arg " .. i .. ": " .. tostring(args[i]))
end
end
process("a", "b", "c")
Expected output:
Received 3 arguments
arg 1: a
arg 2: b
arg 3: c
Method Syntax (Colon Operator)
Lua's colon syntax is syntactic sugar for method calls -- it passes self implicitly.
local person = {
name = "Alice",
greet = function(self, greeting)
return greeting .. ", I'm " .. self.name
end
}
-- Without colon (explicit self)
print(person.greet(person, "Hi")) --> Hi, I'm Alice
-- With colon (implicit self)
print(person:greet("Hi")) --> Hi, I'm Alice
Common Mistakes
1. Forgetting return
Without return, a function returns nil. local result = my_func() silently gets nil.
2. Confusing parameters with arguments
Parameters are the variables in the function definition. Arguments are the values passed when calling.
3. Overlooking that = returns nil
A function with no explicit return returns nil. This surprises developers from languages with implicit returns.
4. Creating closures in loops incorrectly
-- Wrong: all closures share the same i
local funcs = {}
for i = 1, 3 do
funcs[i] = function() print(i) end
end
In Lua 5.3+, this works correctly. In Lua 5.2 and earlier, capture a copy: local j = i.
5. Mixing tab and space indentation
Lua doesn't care about indentation, but readable code matters. Be consistent.
Practice Questions
1. What makes Lua functions "first-class"? Functions are values. They can be stored in variables, passed as arguments, returned from other functions, and stored in tables.
2. What is a closure in Lua? A function that captures local variables from its enclosing scope. The captured variables (upvalues) persist as long as the closure exists.
3. How do you return multiple values from a Lua function?
Separate them with commas after return: return a, b, c. The caller captures them with multiple assignment: local x, y, z = my_func().
4. What does ... mean in a function definition?
It represents variable arguments (varargs). The function can receive any number of arguments, accessed through ... and select().
Challenge: Write a function that takes variable number of numbers, returns the minimum, maximum, and average as three separate values.
FAQ
{{< faq question="Can I define default parameter values?" >}}
Lua doesn't have default parameters built in. Use name = name or "default" inside the function to simulate them. Be careful with false values -- use a stricter check: if name == nil then name = "default" end.
{{< /faq >}}
{{< faq question="What is tail call optimization?" >}}
Lua supports proper tail calls -- if a function ends with return func(args), the current function's stack frame is reused. This enables infinite Recursion without stack overflow for tail-recursive functions.
{{< /faq >}}
{{< faq question="Can I call a function stored in a table?" >}}
Yes. Use table.func(args) for functions stored as values, or table:method(args) for methods that need the self parameter.
{{< /faq >}}
{{< faq question="How do I create an optional parameter?" >}}
Check if the parameter is nil: function greet(name) name = name or "World" ... end. Or use select to count arguments.
{{< /faq >}}
{{< faq question="What is the difference between local function and function?" >}}
local function is scoped to the enclosing block (function-body or file). function foo() assigns to a global. Always use local function unless you specifically need globals.
{{< /faq >}}
Try It Yourself
local function calculator(a, b, op)
local operations = {
add = function(x, y) return x + y end,
sub = function(x, y) return x - y end,
mul = function(x, y) return x * y end,
div = function(x, y)
if y == 0 then return nil, "division by zero" end
return x / y
end
}
local func = operations[op]
if not func then
return nil, "unknown operation: " .. op
end
return func(a, b)
end
-- Test the calculator
local r1, _ = calculator(10, 5, "add")
print("10 + 5 = " .. r1)
local r2, _ = calculator(10, 5, "mul")
print("10 * 5 = " .. r2)
local r3, err = calculator(10, 0, "div")
if err then
print("Error: " .. err)
end
Expected output:
10 + 5 = 15
10 * 5 = 50
Error: division by zero
What's Next
Now that you understand functions, learn about metatables -- Lua's mechanism for operator overloading and prototypal inheritance.
| Topic | Description | Link |
|---|---|---|
| Metatables | Operator overloading and metamethods | {{< ref "06-metatables" >}} |
| OOP | Object-oriented programming with prototypes | {{< ref "07-oop-prototypes" >}} |
| JavaScript Functions | Compare with JavaScript closures | JavaScript |
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro