Error Handling

22.1 Raising Errors

local function divide(a, b)
    if b == 0 then error("division by zero") end          -- adds position info: "file.lua:2: division by zero"
    return a / b
end
error("message", 2)          -- level 2: blame the caller
error("no position", 0)      -- level 0: no position info
error({code = 404, msg = "not found"})   -- any value can be an error object
assert(tonumber("42"), "not a number")   -- returns its arguments if truthy, else raises
local f = assert(io.open("file.txt", "r"))   -- idiom: functions returning nil, err work with assert

22.2 Catching Errors: pcall and xpcall

local ok, result = pcall(divide, 10, 0)
if not ok then print("Error: " .. result) end       --> Error: file.lua:2: division by zero

local ok2, err = pcall(function()
    local t = nil
    return t.field                                  -- runtime error
end)
print(ok2, err)                                     --> false  ...attempt to index a nil value (local 't')

-- xpcall with a message handler (runs before the stack unwinds: good for tracebacks)
local ok3, trace = xpcall(function() error("deep failure") end, debug.traceback)
print(trace)

-- xpcall with arguments (5.2+)
local ok4, value = xpcall(divide, debug.traceback, 10, 2)

-- Error objects
local ok5, e = pcall(error, {code = 500, msg = "server"})
if not ok5 and type(e) == "table" then print(e.code, e.msg) end

22.3 Conventions: nil, err Returns vs Errors

-- Expected failures: return nil plus an error message (like io.open)
local function read_config(path)
    local f, err = io.open(path, "r")
    if not f then return nil, "cannot open config: " .. err end
    local content = f:read("a")
    f:close()
    return content
end
local content, err = read_config("missing.conf")
if not content then print(err) end

-- Programming errors and violated preconditions: raise with error()

22.4 Structured Error Classes and try/catch Helpers

local AppError = {}
AppError.__index = AppError
AppError.__tostring = function(e) return string.format("[%s] %s", e.kind, e.message) end
function AppError.new(kind, message) return setmetatable({kind = kind, message = message}, AppError) end

local function try(f, catch, finally)
    local ok, err = pcall(f)
    if not ok and catch then catch(err) end
    if finally then finally() end
end

try(function() error(AppError.new("NotFound", "user 42 missing")) end,
    function(e) print("caught: " .. tostring(e)) end,
    function() print("cleanup") end)

22.5 Warnings (5.4)

warn("@on")                  -- enable warnings (the lua standalone interpreter starts with them off)
warn("something odd ", "happened")
-- lua -W script.lua enables warnings from the command line