Protected Calls with pcall
Catch errors safely using pcall and interpret return values.
Protected Calls with pcall is a free Lua Academy lesson on CoddyKit — lesson 2 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the Lua Academy learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.
pcall Basics
pcall(f, ...) calls function f with arguments ... in protected mode. If f succeeds, pcall returns true followed by all return values. If f errors, pcall returns false followed by the error message. The program continues either way.
local function risky(x)
if x < 0 then error("negative input: " .. x) end
return math.sqrt(x)
end
local ok, result = pcall(risky, 9)
print(ok, result) -- true 3.0
ok, result = pcall(risky, -1)
print(ok, result) -- false ...: negative input: -1Catching Specific Errors
Check the error value type after pcall to handle different error kinds differently. Table errors have structured data; string errors have a message. Always handle both cases.
local function op(errType)
if errType == "table" then
error({code=42, msg="structured error"})
else
error("plain string error")
end
end
local ok, err = pcall(op, "table")
if not ok then
if type(err) == "table" then
print("Code:", err.code, "Msg:", err.msg)
else
print("String error:", err)
end
endMultiple Return Values on Success
On success, pcall returns all of the protected function's return values after the initial true. Capture them with multiple variables.
local function compute(a, b)
return a + b, a * b, a - b
end
local ok, sum, product, diff = pcall(compute, 6, 4)
if ok then
print("sum:", sum, "product:", product, "diff:", diff)
-- sum: 10 product: 24 diff: 2
endNesting pcall
pcall calls can be nested. Each pcall creates an independent protected scope. An error in an inner pcall is caught by the inner pcall, not the outer one. This lets you implement fallback strategies.
local function tryPrimary()
error("primary failed")
end
local function tryFallback()
return "fallback result"
end
local ok, result = pcall(tryPrimary)
if not ok then
print("Primary failed:", result)
ok, result = pcall(tryFallback)
end
if ok then print("Got:", result) end -- fallback resultpcall with Methods
To call a method (colon syntax) via pcall, pass the function and the object as the first argument. pcall(obj.method, obj, args...) or wrap in a closure.
local obj = {
x = 10,
compute = function(self, n)
if n == 0 then error("zero divisor") end
return self.x / n
end
}
-- Pass method + self explicitly
local ok, v = pcall(obj.compute, obj, 2)
print(ok, v) -- true 5.0
-- Or use a closure
ok, v = pcall(function() return obj:compute(0) end)
print(ok, v) -- false ...zero divisorpcall for IO Safety
Wrap IO operations in pcall to catch unexpected OS errors. Return a consistent result/error pair so callers can handle failures gracefully.
local function safeRead(path)
local ok, result = pcall(function()
local f = assert(io.open(path, "r"))
local content = f:read("a")
f:close()
return content
end)
if ok then return result
else return nil, result
end
end
local data, err = safeRead("config.txt")
if data then print("Read", #data, "bytes")
else print("Error:", err)
endRetry Pattern with pcall
Retry a flaky operation (network request, file write) up to N times, using pcall to catch errors. Sleep between retries in real code (or use coroutines).
local function retry(fn, maxAttempts)
local attempts = 0
while attempts < maxAttempts do
attempts = attempts + 1
local ok, result = pcall(fn)
if ok then
print("Succeeded on attempt", attempts)
return result
end
print("Attempt", attempts, "failed:", result)
end
error("all " .. maxAttempts .. " attempts failed")
end
local n = 0
retry(function()
n = n + 1
if n < 3 then error("not ready") end
return "done"
end, 5)Capturing pcall in Table
When pcall is used in a loop, store results in a table for later analysis. This is useful for batch operations where you want to process all items and collect errors rather than stopping at the first failure.
local jobs = {10, -5, 25, 0, 16}
local results = {}
for _, n in ipairs(jobs) do
local ok, v = pcall(function()
if n < 0 then error("negative") end
return math.sqrt(n)
end)
results[#results+1] = {input=n, ok=ok, value=v}
end
for _, r in ipairs(results) do
if r.ok then
print(r.input, "->", string.format("%.3f", r.value))
else
print(r.input, "-> ERROR:", r.value)
end
endpcall vs xpcall
pcall catches the error but gives you only the error value. xpcall lets you provide a message handler function that runs while the stack is still intact, enabling stack traces. Choose xpcall when you need tracebacks.
-- pcall: simple, no traceback
local ok1, err1 = pcall(function()
error("simple error")
end)
print(err1) -- only the error message
-- xpcall: with traceback handler
local ok2, err2 = xpcall(
function() error("traced error") end,
function(e) return debug.traceback(e, 2) end
)
print(err2) -- full stack tracepcall Return Convention
Use pcall as the basis for a consistent API: always return value on success or nil, errorMessage on failure. Layer your functions this way so callers always know what to expect.
local function loadJSON(path)
local ok, result = pcall(function()
local f = assert(io.open(path, "r"))
local text = f:read("a")
f:close()
-- Fake JSON parse for demo
return {data = text, path = path}
end)
if ok then return result
else return nil, result
end
end
local data, err = loadJSON("data.json")
if data then print("Loaded:", data.path)
else print("Failed:", err)
endQuick Check
What does pcall return when the protected function succeeds?
Recap: pcall
Summary:
pcall(f, ...)→true, results...orfalse, err- Check first return to determine success/failure
- Multiple return values after true on success
- Nested pcall: each catches its own errors independently
- Retry pattern: loop with pcall for flaky operations
- Use xpcall when you need stack traces
Frequently asked questions
Is the “Protected Calls with pcall” lesson free?
Yes — the full text of “Protected Calls with pcall” is free to read here on the web, and the Lua Academy course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the Lua Academy course, upgrade to CoddyKit PRO.
What will I learn in “Protected Calls with pcall”?
Catch errors safely using pcall and interpret return values. You practise Lua Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.
Do I need any experience to start Lua Academy?
No prior experience is required. Lua Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 2 of 4, so you can start here or from the beginning and move at your own pace.
How long does the “Protected Calls with pcall” lesson take?
Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.
Can I write and run code in this Lua Academy lesson?
Yes. Every Lua Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.
All lessons in this course
- The error() Function
- Protected Calls with pcall
- xpcall and Message Handlers
- Structured Error Objects