I’ve been using Lua 5.1 in the past, and I don’t know much about _ENV
in Lua 5.3. Recently, I used Lua 5.3 in a new project, so I looked into it. This article summarizes the meaning of the environment and global variables in Lua 5.3, _ENV
and their usage.
Types of Lua variables
Lua variables can be classified as local, upvalue and global variables. Anyone who has used Lua a lot should be familiar with it, as an example:
For the function foo
, a
and b
are local variables, up
is an upper value, and print
is a global variable.
Local variables and up values
What a variable is is determined at compile time: from the current function upwards, variables that can be found in the current function are local variables, those found in higher-level functions are up values, and those that cannot be found are global variables. Here is Lua’s code for finding variables:
|
|
This code is located in lparser.c
, which is part of the Lua compiler. The comments that come with it are very detailed. First line 9 looks in the local variables of the current function, if it finds one, it sets it to a local variable and stores its index (the first local variable), otherwise it tries to find the upper value. Each upper value found is stored at compile time, including the index of each upper value, its name, whether it is on the stack (i.e., whether it is a local variable of a higher-level function), etc. When searching for an upper value, first line 16 looks for it among the existing upper values, and if it finds it, line 24 sets it to an upper value and returns it; otherwise, line 18 recursively calls singlevaraux
to the upper function. If it is found, there are two results: one is that it is a local variable of the higher-level function, which tells us how many local variables it is (the local variable index); the other is that it is the upper value of the higher-level function, which tells us how many upper values it is (the upper value index). This index is then stored at line 22, and identifies whether it is a local variable (on the stack) or an upper value (not on the stack) of the higher-level function. Finally, if the recursion runs to line 7, which means that all functions nested in the current function do not find this variable, it is considered a global variable and is returned directly at line 20.
At runtime, for local variables, the value of the variable is obtained directly from the index; for upper values, if it is on the stack, the value of the variable is obtained from the local variable of the higher-level function by indexing, otherwise the value of the variable is obtained from the upper value of the higher-level function by indexing. In other words, Lua numbers local variables and upper values at compile time, and gets the variable by number at runtime, without caring about the variable name. (So stop saying that shorter variable names are more efficient)
Global variables
So what does Lua do with global variables that cannot be found in local variables and upper values? This is where it gets really interesting. Let’s look at the caller of singlevaraux
, singlevar
. Lua calls it for every variable it encounters when compiling:
|
|
First singlevar
calls singlevaraux
to find the variable, and if it doesn’t find it, it starts processing the global variable on line 6. Here it first calls singlevaraux
and passes in ls->envn
. A quick look at the code shows that the value of ls->envn
is "_ENV"
, which means it is looking for a variable named _ENV
. Then on line 9 it stores the variable name as a string in the constants section, and on line 10 it generates a table lookup instruction that looks for the value of the variable _ENV
whose key is the variable name.
This means that for each global variable var
, Lua treats it as _ENV.var
. So where does this _ENV
variable come from if we don’t define it manually? We can find out where it is set:
|
|
When calling methods like load
, loadfile
or dofile
, it calls lua_load
, which loads the code and compiles it. First call luaD_protectedparser
on line 23 to compile the code, which will call mainfunc
to compile the main function (or chunk). Notice that on line 8, it sets an upper value variable named _ENV
to the main function. Then on line 31, it sets the global table to the first and only upper value of the main function, namely _ENV
. This global table is created when the Lua virtual machine is initialized and contains various standard library functions.
This means that when we access a global variable, we are actually accessing the key value in the upper value _ENV
; since nested functions inherit the upper value from their parent functions, this makes all functions access the same _ENV
variable, which looks like they share the same global variable. The following code is an example:
For the foo
function, what it actually does on line 2 is _ENV.a = 1
. Here _ENV
inherits from the upper value of the main function, so it prints a
as 1 at the end. The same is true for b
. But for the function bar
, when it reaches lines 10 and 12, it actually sets the key value for the local variable it defined in line 9, so it doesn’t print it at the end.
_G: What about me?
Since a global variable in Lua 5.3 is actually a key in _ENV
, what is _G
? In fact, _G
is a key in _ENV
whose value points to _ENV
itself. That is, there is _ENV._G = _ENV
. This is done purely for compatibility with the old way of writing _G
, which has lost its meaning. It doesn’t matter if you execute _G = nil
, as long as you don’t access _G
manually, it won’t have any effect.
BTW, LuaJIT users used to write local _G = _G
in the file header and explicitly specify _G.var
when using global variables, because it’s faster. In Lua 5.3, this doesn’t make any sense. And it’s even slower if you forget to add local _G = _G
to the file header, because it’s like _ENV._G.var
with an extra table lookup.
Summary
Lua 5.3 defines a non-local, non-supervalued variable var
as _ENV.var
; and sets an initial supernumerary _ENV
for the main function that is equivalent to a table. Nothing else is done to achieve the effect of a global variable. So we can say that the global variables in Lua 5.3 are really just syntactic sugar. This is a beautiful, clean design. Lua is a language that implements Less is batter than more; its source code is a treasure trove for everyone to learn from.