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
b are local variables,
up is an upper value, and
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)
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
singlevar . Lua calls it for every variable it encounters when compiling:
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
"_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
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:
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.
Lua 5.3 defines a non-local, non-supervalued variable
_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.