Acquis 1 - Catch
The catch expression evaluates any expression in protected mode and returns a boolean success flag alongside the result of the expression or an error message.
Syntax
The catch keyword is followed by any expression:
local success, ... = catch <expression>
When the expression evaluates successfully, success is true and ... contains the value of the expression. When the expression raises an error, success is false and ... is instead the error message.
-- Successful evaluation
local ok, val = catch 1 + 2
assert(ok == true)
assert(val == 3)
-- Error during evaluation
local ok, err = catch 1 + "a"
assert(ok == false)
assert(type(err) == "string") -- error message
Return values
The catch expression always returns at least a boolean status, and either the results or error message. When you only need to know whether an expression succeeded, you can ignore the second value:
local ok = catch someRiskyOperation()
if not ok then
-- handle error without needing the message
end
This pattern is particularly useful for assertions when testing error conditions:
-- Assert that an operation fails
assert(not catch type()) -- type() with no args errors
assert(not catch coroutine.yield()) -- yield outside coroutine errors
Function calls
The most common use case is catching errors from function calls:
local ok, result = catch require("no-such-module")
if not ok then
print("Module not found: " .. result)
end
local ok, data = catch load(someCode)
if ok then
data() -- execute the loaded chunk
end
Composition with other Lus features
With if-assignment
The catch expression integrates naturally with Lus’s if-assignment syntax:
if ok, val = catch someOperation() then
print("Success:", val)
else
print("Failed:", val) -- val contains error message here
end
Motivation
Lua’s error handling primitives, pcall and xpcall, operate at the function level. This design has several limitations that become apparent in real-world code.
The anonymous function problem
To protect a single expression, Lua requires wrapping it in an anonymous function:
-- Lua: protecting a single operation
local ok, result = pcall(function()
return someValue + otherValue
end)
This boilerplate obscures the intent and adds cognitive overhead. With catch, the same operation is direct:
-- Lus: direct expression protection
local ok, result = catch someValue + otherValue
Dependency on the global library
pcall and xpcall are global functions, meaning code that uses protected execution depends on the global environment. Sandboxed or restricted environments may not expose these functions, leaving code unable to handle errors gracefully.
The catch keyword is a language construct, not a library function. It works in any environment regardless of what globals are available.
Verbose error checking patterns
Common Lua patterns for error checking become verbose:
-- Lua: checking if an operation would fail
local ok = pcall(function() type() end)
if not ok then
print("type() requires an argument")
end
With catch, this becomes natural:
-- Lus: concise error checking
if not catch type() then
print("type() requires an argument")
end
Testing error conditions
Test suites frequently need to verify that operations fail as expected. The Lua pattern requires nested function calls:
-- Lua: asserting an error
assert(not pcall(function() error("expected") end))
With catch, assertions about errors read more naturally:
-- Lus: asserting an error
assert(not catch error("expected"))