Skip to content

Step 1 of 5 · Reading · ~4 min

Learn

Functions

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 checked_sqrt(x)
    if x < 0 then
        error("negative input", 0)
    end
    return math.sqrt(x)
end

print(pcall(checked_sqrt, 16))
print(pcall(checked_sqrt, -4))

--> true	4.0
--> false	negative input

That trailing 0 matters more than it looks. By default error prefixes the message with the source position — script.lua:12: negative input. 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 fine, value = pcall(function() return 1 + 1 end)
print(fine, value)

local failed, why = pcall(function() error("boom", 0) end)
print(failed, why)

--> 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 problems — tonumber returning nil is not exceptional; it is Tuesday.
  • Passing arguments to the wrong place — pcall(checked_sqrt, 16) passes the argument through; pcall(checked_sqrt(16)) calls checked_sqrt before the protection exists.

Your exercise

Guarded Divide reads a count, then that many lines. Each line should hold two numbers. For each line it reports either the quotient or the reason there is none.

You write the two checks, each raising its own error with error(msg, 0) so the text is exact, and the pcall wrapper that turns a raised error into a printed line. Parse with tonumber and check for nil yourself: do not let Lua's own message reach the output, because its position prefix would depend on where you put the call.

The mistakes the grader will catch: forgetting the 0 (the message arrives as script.lua:7: ...), and putting the failing call outside the pcall, which aborts the whole run on the first bad line instead of reporting it and moving on to the next.

Up nextModules: local tables, return, requireFunctions

Discussion

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

Sign in to post a comment or reply.

Loading…