Skip to content
Errors: error, assert, pcall
step 1/5

Reading — step 1 of 5

Learn

~4 min readFunctions

Errors: error, assert, pcall

So far every function in this course has assumed its input was fine. Real ones cannot. Lua gives you three tools, and the whole design rests on one idea: an error stops the current call chain, and only a caller who explicitly asked to catch it will see it.

Raising one: error

local function divide(a, b)
    if b == 0 then
        error("division by zero", 0)
    end
    return a / b
end

print(pcall(divide, 10, 4))
print(pcall(divide, 1, 0))

--> true	2.5
--> false	division by zero

That trailing 0 matters more than it looks. By default error prefixes the message with the source position — script.lua:12: division by zero. Useful while debugging; ruinous when a test compares your output byte for byte, because the line number changes whenever you move the call. Level 0 says "no prefix".

Catching one: pcall

pcall — protected call — runs a function and converts a crash into a return value. It returns true plus whatever the function returned, or false plus the error:

local ok, res = pcall(function() return 1 + 1 end)
print(ok, res)

local ok2, err = pcall(function() error("boom", 0) end)
print(ok2, err)

--> true	2
--> false	boom

Everything after the first value is the function's own return values, so pcall(f) never needs a wrapper for the success case.

Errors you did not raise

pcall catches Lua's own runtime errors too, which is where you meet the two messages you will see most often in this language:

print(pcall(function() return nil + 1 end))
print(pcall(function() local t = nil; return t.x end))

--> false	script.lua:1: attempt to perform arithmetic on a nil value
--> false	script.lua:2: attempt to index a nil value (local 't')

"Attempt to index a nil value" is the single most common Lua error in existence. It means you reached into something that was not there — usually a table field that was never set, or a function that returned nothing.

assert, the one-liner

assert(value, message) throws when the value is false or nil, and otherwise hands the value straight back — so it is safe to wrap around an expression:

print(assert(7, "unused"))
print(pcall(function() assert(false, "assert says no") end))

--> 7	unused
--> false	script.lua:2: assert says no

Note two things: assert returns all its arguments on success, and its failure message gets the same position prefix error adds. With no message at all, Lua supplies assertion failed!.

Often you do not need any of this

tonumber already tells you about bad input without throwing:

print(tonumber("42"), tonumber("4x"))

--> 42	nil

Prefer the nil-returning function when failure is ordinary. Save error for "this must never happen", and pcall for the boundary where you finally handle it.

Common mistakes

  • Forgetting level 0 — the position prefix makes exact-output comparisons depend on your line numbers.
  • Expecting pcall's second value to be the result on failure — on failure it is the error; check the first value before you use anything else.
  • Wrapping everything in pcall — an error you catch and ignore is worse than a crash, because the program keeps going in a state you never reasoned about.
  • Using error for expected input problemstonumber returning nil is not exceptional; it is Tuesday.
  • Passing arguments to the wrong placepcall(divide, 10, 4) passes them through; pcall(divide(10, 4)) calls divide before the protection exists.

Your exercise

Guarded Divide reads a count, then that many lines. Each line should hold two numbers. Print ok: <quotient to two decimals> when the division works, and error: <message> when it does not.

Two failures to raise yourself, both with error(msg, 0) so the text is exact: division by zero when the divisor is 0, and not two numbers: <the line> when the line does not hold two numbers. Parse with tonumber and check for nil — do not let Lua's own message reach the output, because its position prefix would depend on where you put the call.

10 4 gives ok: 2.50; 5 0 gives error: division by zero; 9 x gives error: not two numbers: 9 x.

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…