0Pricing
Lua Academy · Lesson

The error() Function

Raise errors with error() and understand error levels and messages.

The error() Function is a free Lua Academy lesson on CoddyKit — lesson 1 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the Lua Academy learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.

Raising Errors

error(message, level) raises a Lua error. Execution stops and the error propagates up the call stack until caught by pcall or xpcall, or until it terminates the program. The message can be any value — string, table, number.

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

print(divide(10, 2))    -- 5.0
-- divide(10, 0)        -- ERROR: division by zero

Error Levels

The second argument to error() controls where the error is reported. Level 1 (default) points to the error() call itself. Level 2 points to the caller. Level 0 adds no position info. Use level 2 in library functions to blame the user's code.

local function assertPositive(n, name)
  if n <= 0 then
    error((name or "value") .. " must be positive, got " .. n, 2)
    -- level 2: blame the caller, not this function
  end
  return n
end

local function compute(x)
  assertPositive(x, "x")   -- error points here if x <= 0
  return math.sqrt(x)
end

compute(-5)  -- error: "x must be positive, got -5" at compute() call

Error with Table Objects

Passing a table as the error value lets callers inspect structured error info: error code, message, context. This is more informative than a plain string and allows programmatic error handling.

local function openDB(host, port)
  if port < 1 or port > 65535 then
    error({code="INVALID_PORT", port=port,
           msg="port out of range: " .. port})
  end
  -- ... connect
  return {host=host, port=port}
end

local ok, err = pcall(openDB, "localhost", -1)
if not ok and type(err) == "table" then
  print("Code:", err.code)    -- INVALID_PORT
  print("Port:", err.port)    -- -1
  print("Msg:", err.msg)
end

assert() as Sugar

assert(v, msg) is equivalent to if not v then error(msg, 2) end; return v, .... It's idiomatic for precondition checking. If v is truthy, assert returns all its arguments (useful for chaining).

local function sqrt(n)
  assert(type(n) == "number", "expected number, got " .. type(n))
  assert(n >= 0, "sqrt of negative: " .. n)
  return math.sqrt(n)
end

print(sqrt(16))    -- 4.0
print(sqrt(2))     -- 1.4142...
-- sqrt("hi")      -- ERROR: expected number, got string

error() vs return nil,err

Two conventions for signaling failure: error() (exception-style) or return nil, msg (functional style). Use error() for truly unexpected conditions (programming bugs, contract violations). Use nil, msg for expected failures (file not found, network timeout).

-- Exception style (programming error)
local function mustExist(t, key)
  local v = t[key]
  if v == nil then error("required key missing: " .. key, 2) end
  return v
end

-- Functional style (expected failure)
local function findUser(id)
  -- ... database query
  return nil, "user not found"  -- expected: user may not exist
end

Error in Metamethods

Errors can be raised inside metamethods. If a metamethod errors, it propagates to the code that triggered the operation (like an arithmetic expression). Always guard against invalid inputs in metamethods.

local SafeDiv = {}
SafeDiv.__index = SafeDiv

SafeDiv.__div = function(a, b)
  if b.value == 0 then
    error("SafeDiv: division by zero", 2)
  end
  return SafeDiv.new(a.value / b.value)
end

function SafeDiv.new(v)
  return setmetatable({value=v}, SafeDiv)
end

local a = SafeDiv.new(10)
local b = SafeDiv.new(0)
local ok, err = pcall(function() return a / b end)
print(ok, err)

Custom Error Types

Create a helper to build typed error objects. Include a type tag so callers can distinguish between different kinds of errors and handle each appropriately.

local function newError(kind, msg, extra)
  return setmetatable(
    {kind=kind, message=msg, extra=extra},
    {__tostring = function(e)
      return "[" .. e.kind .. "] " .. e.message
    end}
  )
end

local E = {
  notFound = function(name) return newError("NOT_FOUND","not found: "..name) end,
  badInput = function(msg)  return newError("BAD_INPUT", msg) end,
}

local ok, err = pcall(error, E.notFound("config.json"))
if not ok then print(err.kind, err.message) end

Error Propagation

When a function calls another that errors, the error propagates automatically up the stack. You don't need to re-throw — just don't catch it. Only catch errors at the level where you can meaningfully handle or report them.

local function step3() error("step3 failed") end
local function step2() step3() end
local function step1() step2() end

local ok, err = pcall(step1)
if not ok then
  -- err includes the source location
  print("Caught at top level:", err)
end
-- Error: input:1: step3 failed

Stack Trace with debug.traceback

A plain error() gives one line of context. For a full stack trace, use debug.traceback(msg) as the error value. This is typically done in an xpcall handler.

local function buggy()
  local t = nil
  return t.field   -- nil indexing: error
end

local ok, err = xpcall(buggy, function(e)
  return debug.traceback(e, 2)  -- full stack trace
end)

if not ok then
  print(err)  -- full traceback
end

Re-raising Errors

Sometimes you catch an error to add context, then re-raise it. Use error(err, 0) (level 0) when re-raising a string error to avoid adding another location prefix to an already-formatted message.

local function loadAndParse(path)
  local ok, err = pcall(function()
    local f = io.open(path, "r")
    if not f then error("cannot open: " .. path) end
    local content = f:read("a")
    f:close()
    return content
  end)
  if not ok then
    error("loadAndParse failed: " .. err, 0)  -- re-raise with context
  end
end

local ok2, msg = pcall(loadAndParse, "missing.txt")
print(ok2, msg)

Quick Check

What does level 2 mean in error("msg", 2)?

Recap: error()

Summary:

  • error(msg, level) — raise; level 2 blames caller
  • Use tables for structured errors with type+context
  • assert(v, msg) — idiomatic precondition check
  • Use error() for bugs; return nil+err for expected failures
  • Re-raise with error(err, 0) to preserve message format

Frequently asked questions

Is the “The error() Function” lesson free?

Yes — the full text of “The error() Function” is free to read here on the web, and the Lua Academy course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the Lua Academy course, upgrade to CoddyKit PRO.

What will I learn in “The error() Function”?

Raise errors with error() and understand error levels and messages. You practise Lua Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.

Do I need any experience to start Lua Academy?

No prior experience is required. Lua Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 1 of 4, so you can start here or from the beginning and move at your own pace.

How long does the “The error() Function” lesson take?

Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.

Can I write and run code in this Lua Academy lesson?

Yes. Every Lua Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.

All lessons in this course

  1. The error() Function
  2. Protected Calls with pcall
  3. xpcall and Message Handlers
  4. Structured Error Objects
← Back to Lua Academy