luaguides

error

error raises a runtime error, stopping normal execution and propagating up the call stack until a protected context catches it. It’s how you signal that something has gone wrong and let the caller decide how to handle it.

Signature

error(message [, level])

Arguments:

  • message — the error value, typically a string but can be any Lua type
  • level — which call stack level to blame in the error location (default: 1)

Returns: Nothing — error never returns normally.

Basic Behavior

error stops execution of the current function and propagates the error upward:

function divide(a, b)
    if b == 0 then
        error("division by zero")
    end
    return a / b
end

divide(10, 0)
-- stdin:1: division by zero
-- stack traceback:
--    [C]: in function 'error'
--    stdin:1: in function 'divide'
--    ...

When called inside pcall, the error is caught and returned as the second value:

local ok, err = pcall(function()
    error("something went wrong")
end)

print(ok)  -- false
print(err) -- something went wrong

The Message Can Be Any Value

While strings are the convention, error accepts any Lua value:

error(42)           -- raises the number 42
error({code = "E1"}) -- raises a table

-- pcall receives whatever you pass:
local ok, val = pcall(error, {code = "E1"})
print(val.code)  -- E1

In practice, almost all error messages are strings. But raising tables as error objects is useful when you need structured error data — error codes, metadata, stack context — that the handler can inspect rather than parse from a string.

The Level Parameter

level controls which call site appears in the error’s location information. This is purely cosmetic, but it matters for debugging.

Level 1 (default): blames the line where error was called.

function foo(n)
    if n < 0 then
        error("n must not be negative")  -- blames this line
    end
end

Level 2: blames the caller of the current function — use this when you’re validating arguments and want the error to point at the caller:

function foo(n)
    if type(n) ~= "number" then
        error("number expected, got " .. type(n), 2)
    end
end

foo("hello")
-- stdin:1: number expected, got string
-- (points to the call site, not to line 3 of foo)

Level 0: omits the location entirely — useful when the error message is self-explanatory and the line number would just add noise:

error("config file not found", 0)

Common Patterns

Argument Validation

The most common use of error is validating function arguments:

function set_name(obj, name)
    if type(name) ~= "string" then
        error("name must be a string", 2)
    end
    if #name == 0 then
        error("name cannot be empty", 2)
    end
    obj.name = name
end

Guard Clauses

Use guard clauses to exit early when preconditions fail:

function process(data)
    if not data then
        error("data is required")
    end
    if not data.url then
        error("data.url is required")
    end
    -- proceed with processing
end

Raising Structured Errors

For APIs that need more than a message string:

function validate_config(cfg)
    local errs = {}
    if not cfg.host then table.insert(errs, "host is required") end
    if not cfg.port then table.insert(errs, "port is required") end
    if #errs > 0 then
        error({
            code = "INVALID_CONFIG",
            errors = errs
        })
    end
end

local ok, err = pcall(validate_config, {})
if not ok and type(err) == "table" then
    print(err.code)      -- INVALID_CONFIG
    print(err.errors[1]) -- host is required
end

How error Interacts with pcall and xpcall

error does not return — it performs a longjmp equivalent that unwinds the stack. The nearest active pcall or xpcall catches it:

pcall(function()
    error("oops")
end)
-- returns false, "oops"

xpcall(function()
    error("oops")
end, debug.traceback)
-- returns false, "stack traceback:\n  ..."

xpcall(function()
    error("oops")
end, function(err)
    return err .. " (caught by handler)"
end)
-- returns false, "oops (caught by handler)"

Without a protected context, error terminates the program:

error("fatal")
print("this never runs")
-- program terminates with error message

Common Pitfalls

Forgetting the Level When Validating

If you validate arguments and use the default level, the error points inside your function rather than at the caller:

function foo(n)
    if type(n) ~= "number" then
        error("need a number")  -- default level=1
    end
end

foo("x")
-- stdin:3: need a number  (refers to the error() call inside foo)
-- Caller has no idea which argument was wrong

Fix it with level=2 so the error points to the call site.

Raising nil

If you pass nil to error, Lua replaces it with a default message:

pcall(error, nil)
-- err = "error object is not a string"

This happens because Lua expects error objects to be strings or tables. Passing nil is almost always a bug.

Using error for Control Flow

error is for exceptional states, not normal control flow. If a condition is expected, use a return value instead:

-- Wrong:
function find_item(list, key)
    if not list[key] then
        error("key not found")  -- this happens regularly
    end
    return list[key]
end

-- Better:
function find_item(list, key)
    if not list[key] then
        return nil  -- expected outcome, handle with if/else
    end
    return list[key]
end

See Also