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
| Situation | Use |
|---|---|
| Programmer error (invalid internal state) | assert |
| Expected failure (file missing, user bad input) | error with return |
| Catching errors from unsafe operations | pcall / xpcall |
See Also
- /reference/core-functions/ref-error/ — raise errors explicitly
- /reference/core-functions/ref-pcall/ — protected call that catches errors
- /reference/core-functions/ref-select/ — select elements from a vararg list