Errors and pcall
Raising an error on purpose, protecting a call with pcall, and promising that something is true with assert.
Stopping on purpose
error stops the program and raises a message. You call it when something has gone wrong that the program cannot sensibly continue past: a missing name, a number that cannot be used the way it was intended.
Lua does not check much for you. It trusts your code, so a bad value often only shows up later, when it is used. Raising your own error early is a kindness to whoever reads the output.
Catching with pcall
pcall calls a function in protected mode. Instead of letting an error escape, it returns two values: a boolean saying whether the call worked, and the result or the error message.
The name means protected call. On success the boolean is true and the second value is what the function returned. On failure the boolean is false and the second value is the error that was raised.
assert
assert checks a condition and raises an error if it is not true, using the message you give it. It is a short way to say that something must hold, right at the top of a function.
When the check passes, assert hands back its first argument, so the value can be checked and kept in one line. When it fails, the message you wrote becomes the error that the caller sees.
What unwinding does
error starts an unwind. Lua abandons the current function, then its caller, and so on, discarding each frame and its locals until something catches the error. pcall is that catcher: it turns the jump back into an ordinary return of two values.
Because the frames are gone, code written after the failing line does not run. If a function has to restore something, restore it before the call that might raise, or arrange the work so a failed call happens before anything needs undoing.
The message carries a position
A plain error("bad") prefixes the message with the file and line that raised it. error("bad", 0) leaves the position off, and a level of 2 blames the caller instead. The level is how a function reports the line the reader wrote, rather than the line inside the function.
assert is a thin wrapper: if the condition is true it returns its first argument, and if it is false it raises the message. That makes it useful in the middle of an expression, where a value can be checked and kept in one line.
Arguments, results and xpcall
pcall passes everything after the function to it: pcall(divide, 10, 0) calls divide with 10 and 0. On success it returns true followed by every value the function returned, so a function with two results gives three values from pcall. On failure it returns false and the error.
xpcall takes a handler as its second argument and calls it with the error before returning. That is where a traceback belongs: xpcall(f, debug.traceback) keeps the stack of the failure, which a plain pcall discards. Use it at the top of a program, where a report is more useful than a bare message.
Catch at the boundary
A pcall around every line hides bugs instead of handling them. If a failed call leaves the program in a state it cannot continue from, letting the error out is honest. Catch where you can do something: a request handler that returns a 500, a command loop that reports and asks again, a plugin boundary that must not take the host down.
At those boundaries, decide what the caller sees. A string is fine for a person reading a terminal; a table with a code and a message is easier for code to branch on. Whatever you raise, document it, and keep the message specific enough to name the value that was wrong.
A protected division
local function divide(a, b)
if b == 0 then
error("cannot divide by zero")
end
return a / b
end
print(divide(10, 2))
local ok, message = pcall(divide, 10, 0)
print(tostring(ok))
print(type(message))Three error-handling mistakes. Passing the result of a call to pcall instead of the function — pcall(f()) runs f first, so the error escapes. Treating the second value as always a string when a library may raise a table. And forgetting that a syntax error stops the file before any of it runs, so no pcall inside that file can catch it.
Try these next
- Write a function that raises an error unless its argument is a positive number, and catch it three ways.
- Change an error level from 1 to 2 and compare the position in the message.
- Use
assertto check a value and return it in the same expression.
A call that fails safely
Run it and watch the unprotected call succeed while the protected one returns false. Then change the divisor so the error fires, and catch it with your own pcall.
A check that reports, not crashes
Run it and read the three results. Then add a call with 0 and one with an empty string, and decide which should pass.
A plain error carries a string, but an error can carry any value, including a table of details. When you catch one with pcall, check the type of the second value before you treat it as text.
Check what you learned
One question at a time. A wrong answer explains itself — take another go.
Part 1 of 5
What does pcall return?
