A Common Lua Pitfall : Loading Code
One area of Lua which commonly trips people up is the loading of code. I shall attempt to cover the process of loading code, if only to have something to point people at when they ask in the future.
A typical example
A common misunderstand of how Lua loads code looks something like the following:
I have the following file called
foo.lua
:
function foo(x, y) print(x + y) end
Then I load it like this:
foo = loadfile("foo.lua") foo(2, 3)
I am expecting to see
5
as the output, but nothing happens.
The above example is about as bad as things can get, as if you call foo
a second time, then it'll start doing what was expected, and if on the other hand you replace loadfile
with dofile
, then Lua will complain with "attempt to call global 'foo' (a nil value)".
A typical fix
As an immediate fix, the correct way to load foo.lua
and run the function contained within it is as follows:
loaded_chunk = assert(loadfile("foo.lua"))
loaded_chunk()
foo(2, 3)
To understand what is going on, let us inspect what Lua's global table looks like after each line of execution. To start with, before any lines are executed, we just have the standard library in the global table:
(standard library; print, math, loadstring, string, etc.)
After executing loaded_chunk = assert(loadfile("foo.lua"))
, a new entry has been added to the global table:
(standard library; print, math, loadstring, string, etc.)
loaded_chunk = function(...)
function foo(x, y)
print(x + y)
end
end
After executing loaded_chunk()
, another new entry gets added to the global table:
(standard library; print, math, loadstring, string, etc.)
loaded_chunk = function(...)
function foo(x, y)
print(x + y)
end
end
foo = function(x, y)
print(x + y)
end<
At this point, calls to foo
will do what was originally intended.
What exactly is loadfile
doing?
Provided that the file you ask for can be found, and doesn't contain any syntax errors, all that loadfile
does is return a new function, the body of which is the contents of the file. That is, the following call to loadfile:
loaded_chunk = loadfile("foo.lua")
Is equivalent to: (*)
loaded_chunk = function(...)
--<< Contents of foo.lua here >>--
end
(*) As previously mentioned, they are not equivalent if foo.lua contains a syntax error, or cannot be read. Even exclusing these two cases, they are not technically equivalent, but for explanatory purposes, assume they are.
All the load functions (load
, loadstring
, loadfile
, lua_load
, luaL_loadbuffer
, luaL_loadstring
, and luaL_loadfile
) work in the same way; they take some text, check it for syntax errors, and then either return an error or a function representing the given text. You can think of them as performing a similar role to the compilation step in C programming; they transform text into something executable, without actually executing it. Part of the confusion over loading Lua code comes from this executable thing being a Lua function, and said function (usually) containing other functions. Another common misunderstanding is mixing up the name of a file and the name of a function, and thinking that loading a file called foo.lua
will return the function called foo
from within that file. The filename of a loaded file is not used at all in the process of transforming text into something executable, and is only remembered for use in debugging information.
The role of globals in all this
For any Lua function, there are three ways to pass values in: globals, upvalues, and parameters. Likewise, there are three ways to pass values out: globals, upvalues, and return values (technically, we should be talking about environments rather than globals, and furthermore in Lua 5.2, environments are just upvalues and locals). When loading code, people often tend to forget about everything except globals. In our typical example, the output of foo.lua
is a function which adds two values together and prints the result, and this is passed out in the form of a global variable called foo
.
A commonly used piece of Lua syntactic sugar is the transformation of function foo
into foo = function
, which is another source of misunderstanding (noting that in turn, that is usually syntactic sugar for _ENV["foo"] = function
in Lua 5.2, or something akin to _G["foo"] = function
in Lua 5.0 and 5.1). What people read as defining a function is infact (usually) assigning a new instance of a function to a global variable / an entry in the table of globals. Hence loading a piece of Lua which "defines functions" doesn't result in those functions getting "defined", as loading said piece of Lua actually results in a function, which when executed, just makes some assignments to variables (albeit those variables generally being globals, and the values being assigned generally being functions).
Alternatives to globals
We could instead choose to make foo.lua
pass this function out as a return value, in which case foo.lua
would look like:
return function(x, y)
print(x + y)
end
And usage of foo.lua
would look like:
loaded_chunk = assert(loadfile("foo.lua"))
foo = loaded_chunk()
foo(2, 3)
On the other hand, we could make foo.lua
itself be the function which takes two values and prints their sum, which would make foo.lua
look like:
local x, y = ...
print(x + y)
Remembering that loading a chunk effectively wraps it in function(...)
, so the parameters are accessed via ...
. In this case, usage would be:
foo = assert(loadfile("foo.lua"))
foo(2, 3)
Another look at the typical example
When the typical example was introduced, it was stated that if you call foo
a second time, then it'll start doing what was expected. Bearing in mind what has now been said, it should be clearer why this is the case. The first call to foo
is infact calling foo.lua
, which in turns defines a new global called foo
, hence the second call to foo
is calling a different function to the first call, this time calling the add-and-print function.
Also stated was that if on the other hand you replace loadfile
with dofile
, then Lua will complain with "attempt to call global 'foo' (a nil value)". Again, it should now be clearer as to why this is the case. First recall that dofile
is a function which behaves roughly like:
function dofile(name)
local loaded_chunk = assert(loadfile(name))
return loaded_chunk()
end
In this case, the call to dofile
would load and execute foo.lua
, and this execution would define a global called foo
, but this execution returns no values, meaning that dofile
returns no values, meaning that foo = dofile("foo.lua")
assigns no value (nil
) to the global foo
, thus overwriting the previously set foo
.
The benefits of separating loading from executing
The above information states how things are, but it doesn't attempt to say why things are they way they are. Having a gap between loading a chunk and executing it has several advantages:
- You can check whether the syntax of a piece of code is valid by loading it, without having to worry about the piece of code being malicious and doing nasty things.
- You can change the environment of a piece of code after loading it and prior to running it.
- You can load a piece of code once, and then execute it multiple times, without having pay the price of reloading it each time.
Loading with Lua's C API
The above has mainly been from the point of view of loading code from within Lua. If instead you're using Lua's C API, then things really aren't that different. lua_load
and family do the same thing as load
does; they collect some text, wrap it in function(...) --[[ text here ]] end
, and push onto the stack either this new function, or a (syntax) error message.