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.


14.2 – Declaring Global Variables

Global variables in Lua do not need declarations. Although this is handy for small programs, in larger programs a simple typo can cause bugs that are difficult to find. However, we can change that behavior if we like. Because Lua keeps its global variables in a regular table, we can use metatables to change its behavior when accessing global variables.

A first approach is as follows:

    setmetatable(_G, {
      __newindex = function (_, n)
        error("attempt to write to undeclared variable "..n, 2)
      end,
      __index = function (_, n)
        error("attempt to read undeclared variable "..n, 2)
      end,
    })
After that code, any attempt to access a non-existent global variable will trigger an error:
    > a = 1
    stdin:1: attempt to write to undeclared variable a

But how do we declare new variables? With rawset, which bypasses the metamethod:

    function declare (name, initval)
      rawset(_G, name, initval or false)
    end
The or with false ensures that the new global always gets a value different from nil. Notice that you should define this function before installing the access control, otherwise you get an error: After all, you are trying to create a new global, declare. With that function in place, you have complete control over your global variables:
    > a = 1
    stdin:1: attempt to write to undeclared variable a
    > declare"a"
    > a = 1             -- OK

But now, to test whether a variable exists, we cannot simply compare it to nil; if it is nil, the access will throw an error. Instead, we use rawget, which avoids the metamethod:

    if rawget(_G, var) == nil then
      -- `var' is undeclared
      ...
    end

It is not difficult to change that control to allow global variables with nil value. All we need is an auxiliary table that keeps the names of declared variables. Whenever a metamethod is called, it checks in that table whether the variable is undeclared or not. The code may be like this:

    local declaredNames = {}
    function declare (name, initval)
      rawset(_G, name, initval)
      declaredNames[name] = true
    end
    setmetatable(_G, {
      __newindex = function (t, n, v)
        if not declaredNames[n] then
          error("attempt to write to undeclared var. "..n, 2)
        else
          rawset(t, n, v)   -- do the actual set
        end
      end,
      __index = function (_, n)
        if not declaredNames[n] then
          error("attempt to read undeclared var. "..n, 2)
        else
          return nil
        end
      end,
    })

For both solutions, the overhead is negligible. With the first solution, the metamethods are never called during normal operation. In the second, they may be called, but only when the program accesses a variable holding a nil.