Lua - Functions
Overview
Estimated time: 35–40 minutes
Functions are reusable blocks of code that perform specific tasks. Lua treats functions as first-class values, meaning they can be stored in variables, passed as arguments, and returned from other functions. This tutorial covers all aspects of Lua functions.
Learning Objectives
- Create and call functions with parameters and return values
- Understand variable arguments and multiple return values
- Master local functions and function scope
- Explore closures and higher-order functions
- Apply advanced function patterns and best practices
Prerequisites
- Understanding of Lua variables and scope
- Knowledge of control flow (if statements, loops)
- Basic understanding of tables
Basic Function Definition
There are multiple ways to define functions in Lua:
-- Method 1: function statement
function greet()
print("Hello, World!")
end
-- Method 2: function expression
local say_goodbye = function()
print("Goodbye!")
end
-- Method 3: local function (preferred for local functions)
local function welcome()
print("Welcome to Lua!")
end
-- Call the functions
greet()
say_goodbye()
welcome()
Expected Output:
Hello, World!
Goodbye!
Welcome to Lua!
Functions with Parameters
Functions can accept parameters to make them more flexible:
-- Single parameter
local function greet_person(name)
print("Hello, " .. name .. "!")
end
-- Multiple parameters
local function add_numbers(a, b)
local result = a + b
print(a .. " + " .. b .. " = " .. result)
return result
end
-- Parameters with default values (using or operator)
local function greet_with_title(name, title)
title = title or "Mr./Ms." -- Default value
print("Hello, " .. title .. " " .. name)
end
-- Test the functions
greet_person("Alice")
local sum = add_numbers(5, 3)
print("Returned sum:", sum)
greet_with_title("Bob")
greet_with_title("Carol", "Dr.")
Expected Output:
Hello, Alice!
5 + 3 = 8
Returned sum: 8
Hello, Mr./Ms. Bob
Hello, Dr. Carol
Return Values
Functions can return single or multiple values:
Single Return Value
local function square(x)
return x * x
end
local function is_even(num)
return num % 2 == 0
end
local function get_grade(score)
if score >= 90 then
return "A"
elseif score >= 80 then
return "B"
elseif score >= 70 then
return "C"
else
return "F"
end
end
print("Square of 7:", square(7))
print("Is 10 even?", is_even(10))
print("Grade for 85:", get_grade(85))
Multiple Return Values
-- Function returning multiple values
local function divide_with_remainder(dividend, divisor)
local quotient = dividend // divisor
local remainder = dividend % divisor
return quotient, remainder
end
local function get_name_parts(full_name)
local space_pos = string.find(full_name, " ")
if space_pos then
local first = string.sub(full_name, 1, space_pos - 1)
local last = string.sub(full_name, space_pos + 1)
return first, last
else
return full_name, ""
end
end
-- Using multiple return values
local q, r = divide_with_remainder(17, 5)
print("17 ÷ 5 = " .. q .. " remainder " .. r)
local first_name, last_name = get_name_parts("John Doe")
print("First:", first_name, "Last:", last_name)
-- Ignoring some return values
local quotient = divide_with_remainder(20, 3) -- Only gets first return value
print("Quotient only:", quotient)
Expected Output:
Square of 7: 49
Is 10 even? true
Grade for 85: B
17 ÷ 5 = 3 remainder 2
First: John Last: Doe
Quotient only: 6
Variable Arguments
Functions can accept variable number of arguments using ...
:
-- Function with variable arguments
local function sum_all(...)
local args = {...} -- Pack arguments into table
local total = 0
for i, value in ipairs(args) do
total = total + value
end
return total
end
-- Function to print all arguments
local function print_all(...)
local args = {...}
print("Number of arguments:", #args)
for i, arg in ipairs(args) do
print("Arg " .. i .. ":", arg)
end
end
-- Function combining fixed and variable arguments
local function greet_multiple(greeting, ...)
local names = {...}
for i, name in ipairs(names) do
print(greeting .. ", " .. name .. "!")
end
end
-- Test variable argument functions
print("Sum of 1,2,3,4,5:", sum_all(1, 2, 3, 4, 5))
print("Sum of 10,20:", sum_all(10, 20))
print_all("apple", "banana", "orange")
greet_multiple("Hello", "Alice", "Bob", "Charlie")
Expected Output:
Sum of 1,2,3,4,5: 15
Sum of 10,20: 30
Number of arguments: 3
Arg 1: apple
Arg 2: banana
Arg 3: orange
Hello, Alice!
Hello, Bob!
Hello, Charlie!
Local vs Global Functions
Always prefer local functions for better performance and encapsulation:
-- Global function (avoid unless necessary)
function global_function()
return "I'm global"
end
-- Local function (preferred)
local function local_function()
return "I'm local"
end
-- Functions defined inside other functions
local function outer_function()
local function inner_function()
return "I'm inside outer_function"
end
print("Outer function called")
print("Inner function says:", inner_function())
end
print(global_function())
print(local_function())
outer_function()
-- inner_function is not accessible here
-- print(inner_function()) -- This would cause an error
Expected Output:
I'm global
I'm local
Outer function called
Inner function says: I'm inside outer_function
Functions as First-Class Values
Functions can be stored in variables, tables, and passed as arguments:
-- Storing functions in variables
local function add(a, b)
return a + b
end
local function multiply(a, b)
return a * b
end
-- Store functions in a table
local math_operations = {
add = add,
multiply = multiply,
subtract = function(a, b) return a - b end,
divide = function(a, b)
return b ~= 0 and a / b or "Division by zero"
end
}
-- Using functions from table
print("5 + 3 =", math_operations.add(5, 3))
print("5 * 3 =", math_operations.multiply(5, 3))
print("5 - 3 =", math_operations.subtract(5, 3))
print("5 / 3 =", math_operations.divide(5, 3))
-- Passing functions as arguments
local function apply_operation(func, x, y)
return func(x, y)
end
print("Using apply_operation:")
print("Apply add:", apply_operation(add, 10, 5))
print("Apply multiply:", apply_operation(multiply, 10, 5))
Expected Output:
5 + 3 = 8
5 * 3 = 15
5 - 3 = 2
5 / 3 = 1.6666666666667
Using apply_operation:
Apply add: 15
Apply multiply: 50
Closures
Closures allow functions to access variables from their enclosing scope:
-- Basic closure
local function create_counter()
local count = 0
return function()
count = count + 1
return count
end
end
local counter1 = create_counter()
local counter2 = create_counter()
print("Counter1:", counter1()) -- 1
print("Counter1:", counter1()) -- 2
print("Counter2:", counter2()) -- 1 (independent counter)
print("Counter1:", counter1()) -- 3
-- Closure with parameters
local function create_multiplier(factor)
return function(number)
return number * factor
end
end
local double = create_multiplier(2)
local triple = create_multiplier(3)
print("Double 5:", double(5))
print("Triple 4:", triple(4))
-- More complex closure example
local function create_bank_account(initial_balance)
local balance = initial_balance or 0
return {
deposit = function(amount)
if amount > 0 then
balance = balance + amount
return "Deposited " .. amount .. ". New balance: " .. balance
else
return "Invalid deposit amount"
end
end,
withdraw = function(amount)
if amount > 0 and amount <= balance then
balance = balance - amount
return "Withdrew " .. amount .. ". New balance: " .. balance
else
return "Invalid withdrawal amount or insufficient funds"
end
end,
get_balance = function()
return balance
end
}
end
local account = create_bank_account(100)
print(account.deposit(50))
print(account.withdraw(30))
print("Current balance:", account.get_balance())
Expected Output:
Counter1: 1
Counter1: 2
Counter2: 1
Counter1: 3
Double 5: 10
Triple 4: 12
Deposited 50. New balance: 150
Withdrew 30. New balance: 120
Current balance: 120
Higher-Order Functions
Functions that take other functions as arguments or return functions:
-- Map function (applies function to each element)
local function map(func, list)
local result = {}
for i, value in ipairs(list) do
result[i] = func(value)
end
return result
end
-- Filter function (keeps elements that pass test)
local function filter(predicate, list)
local result = {}
for i, value in ipairs(list) do
if predicate(value) then
table.insert(result, value)
end
end
return result
end
-- Reduce function (combines all elements)
local function reduce(func, list, initial)
local accumulator = initial
for i, value in ipairs(list) do
accumulator = func(accumulator, value)
end
return accumulator
end
-- Test higher-order functions
local numbers = {1, 2, 3, 4, 5}
local squared = map(function(x) return x * x end, numbers)
print("Squared:", table.concat(squared, ", "))
local evens = filter(function(x) return x % 2 == 0 end, numbers)
print("Even numbers:", table.concat(evens, ", "))
local sum = reduce(function(acc, x) return acc + x end, numbers, 0)
print("Sum:", sum)
local product = reduce(function(acc, x) return acc * x end, numbers, 1)
print("Product:", product)
Expected Output:
Squared: 1, 4, 9, 16, 25
Even numbers: 2, 4
Sum: 15
Product: 120
Recursive Functions
Functions that call themselves:
-- Factorial using recursion
local function factorial(n)
if n <= 1 then
return 1
else
return n * factorial(n - 1)
end
end
-- Fibonacci sequence
local function fibonacci(n)
if n <= 2 then
return 1
else
return fibonacci(n - 1) + fibonacci(n - 2)
end
end
-- Binary search (recursive)
local function binary_search(arr, target, left, right)
left = left or 1
right = right or #arr
if left > right then
return nil -- Not found
end
local mid = math.floor((left + right) / 2)
if arr[mid] == target then
return mid
elseif arr[mid] < target then
return binary_search(arr, target, mid + 1, right)
else
return binary_search(arr, target, left, mid - 1)
end
end
-- Test recursive functions
print("5! =", factorial(5))
print("6th Fibonacci number:", fibonacci(6))
local sorted_array = {1, 3, 5, 7, 9, 11, 13, 15}
local position = binary_search(sorted_array, 7)
print("Found 7 at position:", position)
Expected Output:
5! = 120
6th Fibonacci number: 8
Found 7 at position: 4
Practical Function Examples
Utility Functions Library
-- String utilities
local string_utils = {
trim = function(str)
return string.match(str, "^%s*(.-)%s*$")
end,
split = function(str, delimiter)
local result = {}
local pattern = "(.-)" .. delimiter
local last_end = 1
for part in string.gfind(str .. delimiter, pattern) do
table.insert(result, part)
end
return result
end,
capitalize = function(str)
return string.upper(string.sub(str, 1, 1)) .. string.lower(string.sub(str, 2))
end
}
-- Table utilities
local table_utils = {
deep_copy = function(original)
local copy
if type(original) == 'table' then
copy = {}
for key, value in pairs(original) do
copy[key] = table_utils.deep_copy(value)
end
else
copy = original
end
return copy
end,
merge = function(t1, t2)
local result = {}
for k, v in pairs(t1) do
result[k] = v
end
for k, v in pairs(t2) do
result[k] = v
end
return result
end,
keys = function(t)
local keys = {}
for k, v in pairs(t) do
table.insert(keys, k)
end
return keys
end
}
-- Test utility functions
print("Trimmed:", "'" .. string_utils.trim(" hello world ") .. "'")
print("Capitalized:", string_utils.capitalize("lua programming"))
local original = {a = 1, b = {c = 2}}
local copy = table_utils.deep_copy(original)
copy.b.c = 3
print("Original b.c:", original.b.c) -- Still 2
print("Copy b.c:", copy.b.c) -- Now 3
local merged = table_utils.merge({a = 1, b = 2}, {b = 3, c = 4})
local keys = table_utils.keys(merged)
print("Merged keys:", table.concat(keys, ", "))
Expected Output:
Trimmed: 'hello world'
Capitalized: Lua programming
Original b.c: 2
Copy b.c: 3
Merged keys: a, b, c
Common Pitfalls
- Global functions: Always use
local function
unless you need global access - Missing return: Functions without explicit return statements return
nil
- Variable scope: Variables inside functions are local to that function
- Stack overflow: Be careful with deep recursion
- Closure gotchas: Closures capture variables by reference, not value
Checks for Understanding
- What are the three ways to define a function in Lua?
- How do you return multiple values from a function?
- What does
...
represent in function parameters? - What is a closure and how is it useful?
- How do you pass a function as an argument to another function?
Show answers
- Function statement (
function name()
), function expression (local f = function()
), and local function (local function name()
) - Use multiple values in the return statement:
return value1, value2, value3
- Variable arguments - allows a function to accept any number of arguments
- A closure is a function that captures variables from its enclosing scope. Useful for creating specialized functions and maintaining private state.
- Just pass the function name as an argument:
my_function(other_function, args)
Next Steps
Functions are fundamental to Lua programming. Now that you've mastered functions, you're ready to explore arrays and advanced table manipulation techniques.