luaguides

assert

assert stops execution and raises an error if its first argument is false or nil. It passes through the argument unchanged if the condition is truthy, making it useful for validating inputs and checking that functions return expected values.

Basic Usage

-- Stop if condition is false/nil
assert(true)              -- passes
assert(1)                  -- passes (truthy)
assert(false)              -- error: assertion failed!
assert(nil)                -- error: assertion failed!

-- With a custom message
assert(x > 0, "x must be positive")
-- error: x must be positive

When assert fails, it raises an error with the provided message. If no message argument is given, it defaults to "assertion failed!". The error behaves like any Lua error: it unwinds the call stack until something catches it with pcall or xpcall. Without a protected call, the error terminates the program with a traceback.

Validating function return values

The most common real-world use of assert is wrapping function calls that return nil on failure — file operations, database queries, or JSON parsing. Instead of writing an if not result then error(...) block after every call, you wrap the call in assert and let it raise the error for you:

local file = assert(io.open("data.txt", "r"))
-- Raises error if file cannot be opened

local result = assert(database.query("SELECT * FROM users"))
-- Raises error if query returns nil/false

The key insight: assert does not catch errors. It raises one when something is wrong. For catching errors from functions that might fail, use pcall or xpcall instead.

Multiple return values

assert returns all its arguments when the condition passes. This is useful for chaining:

local ok, result = assert(SomeFunction())
-- If SomeFunction() returns (nil, "error"), assert raises and never returns

local ok, err = assert(io.open("file.txt"))
-- ok = file object, err = nil

When assert succeeds, it passes through all of its arguments unchanged — the condition value is returned first, followed by any extra values the wrapped expression produced. This pass-through behaviour is what makes assert so convenient for chaining: you can wrap a function call in assert without disrupting the return values that the caller expects. A common pattern is wrapping json.decode or similar parsers that return (nil, error_message) on failure:

local data = assert(json.decode(text))
-- Returns decoded data if decode succeeds

Custom error messages

The optional second argument to assert is the error message. Leaving it out produces a generic "assertion failed!" that tells the reader almost nothing about what went wrong. Always provide a descriptive message that names the expected condition and, when possible, includes the actual value that failed the check. A good assertion message lets someone reading the error immediately understand what was expected and what they got instead:

assert(user_id > 0, "user_id must be positive, got " .. tostring(user_id))
assert(type(data) == "table", "expected table, got " .. type(data))
assert(#players > 0, "at least one player required")

Type Checking

Lua’s dynamic type system means you can pass anything to any function — there’s no compile-time checking to catch mistakes. Placing assert calls at function boundaries gives you a lightweight form of runtime type checking. Each assertion documents the expected type contract and catches type errors at the earliest possible moment, before invalid data propagates deeper into the program where the failure would be harder to diagnose:

local function process_data(data)
    assert(type(data) == "table", "data must be a table")
    assert(type(data.name) == "string", "data.name must be a string")
    assert(type(data.age) == "number", "data.age must be a number")

    -- proceed with processing
end

When not to use assert

assert is designed for programmer errors — invariants that should never be violated in correct code. It raises a raw error with a traceback, which is exactly what you want during development but terrible for end users. Using assert on user input or expected runtime failures subjects users to cryptic stack traces instead of helpful messages. Know the boundary: use assert to catch your own bugs, not to handle the outside world.

User input validation:

-- WRONG: User input goes in, this raises a raw error
assert(tonumber(age), "Invalid age")

-- RIGHT: Handle the case gracefully
local age = tonumber(age_input)
if not age then
    print("Please enter a valid number")
    return
end

The version on the right handles the error condition without crashing — it prints a helpful message and returns control to the caller. This distinction between programmer errors (use assert) and user-facing errors (use if + return) is one of the most important design decisions in Lua error handling.

Expected failures:

-- WRONG: Expected case that calling code handles
assert(file_exists, "File not found")

-- RIGHT: Return nil/error for expected failures
local file = io.open(filename)
if not file then
    return nil, "File not found"
end

assert vs error

SituationUse
Programmer error (invalid internal state)assert
Expected failure (file missing, user bad input)error with return
Catching errors from unsafe operationspcall / xpcall

See Also