This first edition was written for Lua 5.0. While still largely relevant for later versions, there are some differences.
The fourth edition targets Lua 5.3 and is available at Amazon and other bookstores.
By buying the book, you also help to support the Lua project.


8.3 – Errors

Errare humanum est. Therefore, we must handle errors the best way we can. Because Lua is an extension language, frequently embedded in an application, it cannot simply crash or exit when an error happens. Instead, whenever an error occurs, Lua ends the current chunk and returns to the application.

Any unexpected condition that Lua encounters raises an error. Errors occur when you (that is, your program) try to add values that are not numbers, to call values that are not functions, to index values that are not tables, and so on. (You can modify this behavior using metatables, as we will see later.) You can also explicitly raise an error calling the error function; its argument is the error message. Usually, that function is the appropriate way to handle errors in your code:

    print "enter a number:"
    n = io.read("*number")
    if not n then error("invalid input") end
Such combination of if not ... then error end is so common that Lua has a built-in function just for that job, called assert:
    print "enter a number:"
    n = assert(io.read("*number"), "invalid input")
The assert function checks whether its first argument is not false and simply returns that argument; if the argument is false (that is, false or nil), assert raises an error. Its second argument, the message, is optional, so that if you do not want to say anything in the error message, you do not have to. Beware, however, that assert is a regular function. As such, Lua always evaluates its arguments before calling the function. Therefore, if you have something like
    n = io.read()
    assert(tonumber(n),
           "invalid input: " .. n .. " is not a number")
Lua will always do the concatenation, even when n is a number. It may be wiser to use an explicit test in such cases.

When a function finds an unexpected situation (an exception), it can assume two basic behaviors: It can return an error code (typically nil) or it can raise an error, calling the error function. There are no fixed rules for choosing between those two options, but we can provide a general guideline: An exception that is easily avoided should raise an error; otherwise, it should return an error code.

For instance, let us consider the sin function. How should it behave when called on a table? Suppose it returns an error code. If we need to check for errors, we would have to write something like

    local res = math.sin(x)
    if not res then     -- error
      ...
However, we could as easily check this exception before calling the function:
    if not tonumber(x) then     -- error: x is not a number
      ...
Usually, however, we check neither the argument nor the result of a call to sin; if the argument is not a number, it means probably something wrong in our program. In such situations, to stop the computation and to issue an error message is the simplest and most practical way to handle the exception.

On the other hand, let us consider the io.open function, which opens a file. How should it behave when called to read a file that does not exist? In this case, there is no simple way to check for the exception before calling the function. In many systems, the only way of knowing whether a file exists is to try to open it. Therefore, if io.open cannot open a file because of an external reason (such as "file does not exist" or "permission denied"), it returns nil, plus a string with the error message. In this way, you have a chance to handle the situation in an appropriate way, for instance by asking the user for another file name:

    local file, msg
    repeat
      print "enter a file name:"
      local name = io.read()
      if not name then return end   -- no input
      file, msg = io.open(name, "r")
      if not file then print(msg) end
    until file
If you do not want to handle such situations, but still want to play safe, you simply use assert to guard the operation:
    file = assert(io.open(name, "r"))
This is a typical Lua idiom: If io.open fails, assert will raise an error.
    file = assert(io.open("no-file", "r"))
      --> stdin:1: no-file: No such file or directory
Notice how the error message, which is the second result from io.open, goes as the second argument to assert.