Lua Functions

“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

:warning: 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.

:light_bulb: 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 local for 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