Skip to main content

Error Handling

LuaLuaA lightweight, embeddable scripting language used as the primary scripting language in FiveM server development. uses protected calls rather than try/catch. Errors are values (usually strings).


error(message [, level])

Raises an error. level controls where the error is attributed:

error("something broke") -- level 1: current function
error("bad arg", 2) -- level 2: calling function

assert(v [, message])

Checks that v is truthy. Returns v if ok, raises error if not.

local f = assert(io.open("file.txt", "r")) -- error if file not found
assert(type(n) == "number", "expected number")

Returns all arguments on success (useful for passing through):

local a, b, c = assert(multi_return_func())

pcall(f [, arg1, ...])

Protected call. Calls f in protected mode.

local ok, result = pcall(function()
error("bad thing")
end)
print(ok) -- false
print(result) -- bad thing

Return values

  • true, f() return values... on success
  • false, error_object on failure
local ok, a, b = pcall(function() return 1, 2 end)
-- ok=true, a=1, b=2

local ok, err = pcall(function() error({code=42}) end)
-- ok=false, err={code=42} -- error can be any value

xpcall(f, msgh [, arg1, ...])

Like pcall but with a message handler to transform/process the error.

local ok, result = xpcall(
function() error({detail = "oops"}) end,
function(err)
-- Transform error (e.g., add traceback)
return debug.traceback(tostring(err), 2)
end
)
print(result) -- includes stack trace

Common pattern: error with traceback

function try(f, ...)
return xpcall(f, debug.traceback, ...)
end

local ok, err = try(function()
error("fail")
end)
if not ok then
print(err) -- full traceback
end

Error objects

In Lua 5.4LuaA lightweight, embeddable scripting language used as the primary scripting language in FiveM server development., errors can be any value, not just strings:

-- String error (traditional)
error("file not found")

-- Table error (structured)
error({code = 404, message = "file not found", path = "/data"})

-- Number error
error(42)

[!tip] When using structured errors, always tostring() them before displaying:

local ok, err = pcall(risky)
if not ok then
print("Error: " .. tostring(err))
end

Protected calls across boundaries

In coroutines

local co = coroutine.create(function()
error("oops")
end)
local ok, err = coroutine.resume(co) -- catches error

In C API

if (lua_pcall(L, nargs, nresults, 0) != LUA_OK) {
const char *err = lua_tostring(L, -1);
// handle error
}

Common patterns

Safe require

local function safe_require(mod)
local ok, result = pcall(require, mod)
if ok then return result end
return nil, result -- return nil + error message
end

Retry with backoff

function retry(f, max_attempts, delay)
local attempt = 1
while true do
local ok, result = pcall(f)
if ok then return result end
if attempt >= max_attempts then
error("failed after " .. attempt .. " attempts: " .. tostring(result))
end
attempt = attempt + 1
-- simple busy-wait (use os.execute("sleep") in real code)
for _ = 1, delay * 1000 do end
delay = delay * 2 -- exponential backoff
end
end

Cleanup with pcall

function with_cleanup(f, cleanup)
local ok, result = pcall(f)
cleanup() -- always runs
if not ok then error(result) end
return result
end

Rethrow with context

function wrap_context(f, context)
return function(...)
local ok, err = pcall(f, ...)
if not ok then
error({context = context, original = err})
end
end
end

error in C API

// Raise error from C
luaL_error(L, "bad argument #%d to '%s', expected %s",
arg, funcname, expected);

// Check and raise
luaL_checkinteger(L, 1); -- checks arg 1 is integer, raises if not
luaL_checkstring(L, 2); -- checks arg 2 is string
luaL_argcheck(L, cond, arg, msg); -- custom condition

// Type error
luaL_typeerror(L, arg, "table"); -- "bad argument #1, expected table, got ..."

Summary

FunctionCatches errorsMessage handlerUse case
pcallBasic protected call
xpcallAdd traceback, transform errors
assertQuick validation, raise if nil/false
errorRaise an error
coroutine.resumeErrors in coroutines