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 – Compilation, Execution, and Errors

Although we refer to Lua as an interpreted language, Lua always precompiles source code to an intermediate form before running it. (This is not a big deal: Most interpreted languages do the same.) The presence of a compilation phase may sound out of place in an interpreted language like Lua. However, the distinguishing feature of interpreted languages is not that they are not compiled, but that any compiler is part of the language runtime and that, therefore, it is possible (and easy) to execute code generated on the fly. We may say that the presence of a function like dofile is what allows Lua to be called an interpreted language.

Previously, we introduced dofile as a kind of primitive operation to run chunks of Lua code. The dofile function is actually an auxiliary function; loadfile does the hard work. Like dofile, loadfile also loads a Lua chunk from a file, but it does not run the chunk. Instead, it only compiles the chunk and returns the compiled chunk as a function. Moreover, unlike dofile, loadfile does not raise errors, but instead returns error codes, so that we can handle the error. We could define dofile as follows:

    function dofile (filename)
      local f = assert(loadfile(filename))
      return f()
    end
Note the use of assert to raise an error if loadfile fails.

For simple tasks, dofile is handy, as it does the whole job in one call. However, loadfile is more flexible. In case of errors, loadfile returns nil plus the error message, which allows us to handle the error in customized ways. Moreover, if we need to run a file several times, we can call loadfile once and call its result several times. This is much cheaper than several calls to dofile, because the program compiles the file only once.

The loadstring function is similar to loadfile, except that it reads its chunk from a string, not from a file. For instance, after the code

    f = loadstring("i = i + 1")
f will be a function that, when invoked, executes i = i + 1:
    i = 0
    f(); print(i)   --> 1
    f(); print(i)   --> 2
The loadstring function is powerful; it must be used with care. It is also an expensive function (when compared to its alternatives) and may result in incomprehensible code. Before you use it, make sure that there is no simpler way to solve the problem at hand.

Lua treats any independent chunk as the body of an anonymous function. For instance, for the chunk "a = 1", loadstring returns the equivalent of

    function () a = 1 end
Like any other function, chunks can declare local variables and return values:
    f = loadstring("local a = 10; return a + 20")
    print(f())          --> 30

Both loadstring and loadfile never raise errors. In case of any kind of error, both functions return nil plus an error message:

    print(loadstring("i i"))
      --> nil     [string "i i"]:1: `=' expected near `i'
Moreover, both functions never have any kind of side effect. They only compile the chunk to an internal representation and return the result, as an anonymous function. A common mistake is to assume that loadfile (or loadstring) defines functions. In Lua, function definitions are assignments; as such, they are made at runtime, not at compile time. For instance, suppose we have a file foo.lua like this:
    -- file `foo.lua'
    function foo (x)
      print(x)
    end
We then run the command
    f = loadfile("foo.lua")
After this command, foo is compiled, but it is not defined yet. To define it, you must run the chunk:
    f()           -- defines `foo'
    foo("ok")     --> ok

If you want to do a quick-and-dirty dostring (i.e., to load and run a chunk) you may call the result from loadstring directly:

    loadstring(s)()
However, if there is any syntax error, loadstring will return nil and the final error message will be an "attempt to call a nil value". For clearer error messages, use assert:
    assert(loadstring(s))()

Usually, it does not make sense to use loadstring on a literal string. For instance, the code

    f = loadstring("i = i + 1")
is roughly equivalent to
    f = function () i = i + 1 end
but the second code is much faster, because it is compiled only once, when the chunk is compiled. In the first code, each call to loadstring involves a new compilation. However, the two codes are not completely equivalent, because loadstring does not compile with lexical scoping. To see the difference, let us change the previous examples a little:
    local i = 0
    f = loadstring("i = i + 1")
    g = function () i = i + 1 end
The g function manipulates the local i, as expected, but f manipulates a global i, because loadstring always compiles its strings in a global environment.

The most typical use of loadstring is to run external code, that is, pieces of code that come from outside your program. For instance, you may want to plot a function defined by the user; the user enters the function code and then you use loadstring to evaluate it. Note that loadstring expects a chunk, that is, statements. If you want to evaluate an expression, you must prefix it with return, so that you get a statement that returns the value of the given expression. See the example:

    print "enter your expression:"
    local l = io.read()
    local func = assert(loadstring("return " .. l))
    print("the value of your expression is " .. func())

The function returned by loadstring is a regular function, so you can call it several times:

    print "enter function to be plotted (with variable `x'):"
    local l = io.read()
    local f = assert(loadstring("return " .. l))
    for i=1,20 do
      x = i   -- global `x' (to be visible from the chunk)
      print(string.rep("*", f()))
    end

In a production-quality program that needs to run external code, you should handle any errors reported by loadstring. Moreover, if the code cannot be trusted, you may want to run the new chunk in a protected environment, to avoid unpleasant side effects when running the code.