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:

1
2
3
4
5
local up = "str"
local function foo(a)
    local b = a
    print(up, b)
end

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:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/*
  Find variable with given name 'n'. If it is an upvalue, add this
  upvalue into all intermediate functions.
*/
static void singlevaraux (FuncState *fs, TString *n, expdesc *var, int base) {
  if (fs == NULL)  /* no more levels? */
    init_exp(var, VVOID, 0);  /* default is global */
  else {
    int v = searchvar(fs, n);  /* look up locals at current level */
    if (v >= 0) {  /* found? */
      init_exp(var, VLOCAL, v);  /* variable is local */
      if (!base)
        markupval(fs, v);  /* local will be used as an upval */
    }
    else {  /* not found as local at current level; try upvalues */
      int idx = searchupvalue(fs, n);  /* try existing upvalues */
      if (idx < 0) {  /* not found? */
        singlevaraux(fs->prev, n, var, 0);  /* try upper levels */
        if (var->k == VVOID)  /* not found? */
          return;  /* it is a global */
        /* else was LOCAL or UPVAL */
        idx  = newupvalue(fs, n, var);  /* will be a new upvalue */
      }
      init_exp(var, VUPVAL, idx);  /* new or old upvalue */
    }
  }
}

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:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
static void singlevar (LexState *ls, expdesc *var) {
  TString *varname = str_checkname(ls);
  FuncState *fs = ls->fs;
  singlevaraux(fs, varname, var, 1);
  if (var->k == VVOID) {  /* global name? */
    expdesc key;
    singlevaraux(fs, ls->envn, var, 1);  /* get environment variable */
    lua_assert(var->k != VVOID);  /* this one must exist */
    codestring(ls, &key, varname);  /* key is variable name */
    luaK_indexed(fs, var, &key);  /* env[varname] */
  }
}

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:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
/* lparser.c */
static void mainfunc (LexState *ls, FuncState *fs) {
  BlockCnt bl;
  expdesc v;
  open_func(ls, fs, &bl);
  fs->f->is_vararg = 1;  /* main function is always declared vararg */
  init_exp(&v, VLOCAL, 0);  /* create and... */
  newupvalue(fs, ls->envn, &v);  /* ...set environment upvalue */
  luaX_next(ls);  /* read first token */
  statlist(ls);  /* parse main body */
  check(ls, TK_EOS);
  close_func(ls);
}

/* lapi.c */
LUA_API int lua_load (lua_State *L, lua_Reader reader, void *data,
                      const char *chunkname, const char *mode) {
  ZIO z;
  int status;
  lua_lock(L);
  if (!chunkname) chunkname = "?";
  luaZ_init(L, &z, reader, data);
  status = luaD_protectedparser(L, &z, chunkname, mode);
  if (status == LUA_OK) {  /* no errors? */
    LClosure *f = clLvalue(L->top - 1);  /* get newly created function */
    if (f->nupvalues >= 1) {  /* does it have an upvalue? */
      /* get global table from registry */
      Table *reg = hvalue(&G(L)->l_registry);
      const TValue *gt = luaH_getint(reg, LUA_RIDX_GLOBALS);
      /* set global table as 1st upvalue of 'f' (may be LUA_ENV) */
      setobj(L, f->upvals[0]->v, gt);
      luaC_upvalbarrier(L, f->upvals[0]);
    }
  }
  lua_unlock(L);
  return status;
}

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:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
local function foo()
    a = 1
    (function()
      b = "str"
    end)()
end

local function bar()
    local _ENV = {}
    c = 3.14
    (function()
      d = "str"
    end)()
end

foo()
bar()

print(a, b, c, d) -- 1 str nil nil

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.