Lua is known for being lightweight, embeddable, and flexible—and its approach to error handling is no different. Whether you're writing a game script, a plugin, or a CLI tool, understanding how Lua handles errors is essential for writing clean, maintainable code.
In this article, we'll explore:
- The basic mechanisms of error handling in Lua
- When to use
error()
vs returningnil, err
- Best practices for writing robust functions and libraries
Lua provides two core primitives for error handling:
error(message)
— raises a runtime error with a given messagepcall(func)
— callsfunc
in protected mode, returning a success flag and result
Example:
function might_fail(x)
if x < 0 then
error("Negative not allowed")
end
return x * 2
end
local ok, result = pcall(might_fail, -1)
if not ok then
print("Caught error:", result)
end
Here, pcall
safely captures the error, allowing your program to recover instead of halting execution.
For operations that are expected to fail under normal conditions (e.g., network requests, file I/O, database queries), the idiomatic Lua pattern is:
local res, err = some_function(...)
if not res then
print("Failed:", err)
end
This is widely used in the standard libraries (like io.open
, os.rename
, etc.):
local file, err = io.open("missing.txt", "r")
if not file then
print("Error:", err)
end
This allows calling code to decide whether and how to recover from the failure.
If someone calls your function incorrectly—like passing a number instead of a string—you should not return nil, err
. Instead, raise an error directly using error()
.
function greet(name)
if type(name) ~= "string" then
error("bad argument #1 to 'greet' (string expected, got " .. type(name) .. ")", 2)
end
print("Hello, " .. name)
end
Why? Because this is a programmer error, not a runtime failure. It should fail loudly so it’s noticed and fixed during development.
Tip: The second argument to
error()
(2
in the example above) adjusts the stack level so the error points to the caller, not inside your function. This results in cleaner error messages.
A well-designed Lua function might use both approaches depending on the nature of the problem:
function fetch_data(url)
if type(url) ~= "string" then
error("bad argument #1 to 'fetch_data' (string expected)", 2)
end
-- Simulated network failure
if url == "http://fail" then
return nil, "network unreachable"
end
return "some data"
end
This approach is idiomatic because it distinguishes invalid use of the API (handled with error
) from valid usage that fails (handled with nil, err
).
Scenario | What to do | Why |
---|---|---|
Network/file/database failure | return nil, err |
Expected, recoverable |
Invalid input | error("bad argument") |
Programming bug; fail fast |
Use by library users | if not res then ... |
Check and handle nil, err |
Use in internal functions | Raise with error() |
Catch incorrect usage early |
Lua’s flexibility gives you the power to choose between exception-like behavior (error()
) and more traditional error returns (nil, err
). But with great power comes great responsibility. Distinguishing clearly between expected operational errors and programmer mistakes is key to writing idiomatic and maintainable Lua code.
Follow this guideline, and your Lua APIs will feel clean, predictable, and Lua-like—just the way they should.