Error Handling: pcall and error
By the end of this lesson you'll raise errors with error and assert , catch them with protected calls pcall and xpcall , read the (ok, result) return convention, raise structured error objects instead of bare strings, and write cleanup that runs even on failure — because Lua has no try/catch , and pcall is the idiom.
Learn Error Handling: pcall and error in our free Lua course — an interactive lesson with worked examples, a practice exercise and a quick reference.
Part of the free Lua course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
Think of pcall as a safety net under a trapeze . The acrobat (your risky function) performs as normal; if they slip and fall, the net catches them instead of letting them hit the floor and stopping the whole show. pcall tells you afterward whether the act succeeded ( true and the result) or whether someone fell ( false and what went wrong). Lua never installs the net for you automatically — there is no try/catch — so you decide exactly which acts get a net under them and which are allowed to crash the program.
1. Raising Errors: error & assert
error(msg) deliberately raises an error, stopping normal flow and propagating up until a protected call catches it; by default Lua prefixes the file:line where you called it. assert(v, msg) is the guard-clause shortcut — if v is nil or false it calls error(msg) , otherwise it returns its arguments untouched. The optional level argument to error chooses whose position is blamed: 1 (default) the call site, 2 the caller, 0 no position at all. Read and run this.
2. Catching Errors with pcall
Lua has no try/catch . Instead, pcall(fn, ...) runs fn in protected mode so an error can't escape. On success it returns true followed by fn 's return values; on failure it returns false and the error value. This is the famous (ok, result) convention: capture both, then branch on ok . You can protect a named function, an inline anonymous function for a whole block of risky work, or any call that might blow up — pcall always hands control back to you.
Your turn. Protect a division with pcall , read both returns, and handle a divide-by-zero. Fill in the blanks marked ___ .
3. xpcall, Error Objects & Cleanup
xpcall is pcall with a message handler that runs at the error before the stack unwinds — pass debug.traceback to capture a full stack trace for logging. Errors don't have to be strings: error accepts any value , so you can raise a table carrying a code and message that callers inspect. And since Lua has no finally , you write cleanup by running risky work through pcall , releasing resources unconditionally afterward, then re-raising with error(result, 0) if it failed.
Now you try raising errors. Validate an age with assert and error , then watch the protected calls report each outcome. Fill in the blanks:
No blanks this time — just a brief and an outline. You'll wrap a risky parse in pcall and return the (ok, result) convention yourself, optionally carrying a structured error table. Build it, run it, and check your output.
Practice quiz
Does Lua have try/catch statements?
- No, you use pcall instead
- Only in Lua 5.4
- Only with a library
- Yes, try and catch keywords
Answer: No, you use pcall instead. Lua has no try/catch; the idiom is a protected call with pcall (and xpcall).
What does pcall return when the protected function succeeds?
- Just the result
- false then the result
- true then the function's return values
- An error object
Answer: true then the function's return values. On success pcall returns true followed by whatever the function returned.
What does pcall return when the protected function raises an error?
- An empty string
- false and the error message
- true and nil
- It re-raises the error
Answer: false and the error message. On failure pcall returns false and the error value, instead of letting the error propagate.
Which function deliberately raises an error?
- fail(msg)
- throw(msg)
- raise(msg)
- error(msg)
Answer: error(msg). error(message) stops normal flow and propagates the error up to the nearest pcall.
What does assert(v, msg) do when v is nil or false?
- Calls error with msg
- Prints msg and continues
- Does nothing
- Returns v
Answer: Calls error with msg. assert raises an error using msg when its first argument is falsy; otherwise it returns its arguments.
What does the 'level' argument to error(msg, level) control?
- The output color
- Which stack frame's position is reported
- The log severity
- The retry count
Answer: Which stack frame's position is reported. level controls where the position info points: 1 is the caller of error, 2 blames the caller's caller, 0 adds no position.
How does xpcall differ from pcall?
- It is asynchronous
- It only works on coroutines
- It cannot catch errors
- It takes a message handler that runs at the point of error
Answer: It takes a message handler that runs at the point of error. xpcall takes a handler function (often to capture a traceback) invoked before the stack unwinds.
Can the error VALUE be something other than a string?
- Only numbers
- No, only strings
- Yes, it can be any value such as a table
- Only booleans
Answer: Yes, it can be any value such as a table. error accepts any value, so you can raise a structured table as an error object.
What is the common convention for functions that may fail without raising?
- Return -1
- Return nil plus an error message
- Always return true
- Print and exit
Answer: Return nil plus an error message. Many library functions return nil (or false) plus a message so callers can check without pcall, e.g. io.open.
How do you run cleanup code whether or not an error occurred?
- Run the work via pcall, then clean up afterward using the result
- Lua has a finally block
- It is impossible in Lua
- Use goto cleanup only
Answer: Run the work via pcall, then clean up afterward using the result. Without finally, you call the risky work via pcall, capture ok/result, run cleanup, then re-raise or return as needed.