Lua Scope

Scope Tutorial

lua-users home wiki

Until now you just assigned values to names, and could get back the value by using the name anywhere in the script. This is fine for small examples, but now that you know functions, it can be a big problem: what if different functions use the same name to store temporary values? They will conflict and overwrite each other, making your script an impossible-to-debug mess. The solution is to control where your variables exist using the local keyword.

Interactive interpreter note

The examples in this page will be written in the form of a script file instead of an interactive interpreter session, since local variables are really hard to work with in it. It will be explained why later.

Creating local variables

To create local variables, add the local keyword before the assignment:

local a = 5
print(a)

You don’t need the local keyword any more when changing the variable:

local a = 5
a = 6 -- changes the local a, doesn't create a global

Local variables only exist in the block they were created in. Outside of the block, they do not exist any more.

local a = 5
print(a) --> 5

do
  local a = 6 -- create a new local inside the do block instead of changing the existing a
  print(a) --> 6
end

print(a) --> 5

The place where a variable is visible is called the “scope” of a variable.

Now let’s use functions to show how this is really useful:

function bar()
  print(x) --> nil
  local x = 6
  print(x) --> 6
end

function foo()
  local x = 5
  print(x) --> 5
  bar()
  print(x) --> 5
end

foo()

As you can see, each variable is visible from the point where it’s declared to the end of the block it’s declared in. Even though bar’s x exists at the same time as foo’s x, they’re not written in the same block, so they’re independent. This is what’s called lexical scoping.

local function syntax sugar

local function f() end

-- is equivalent to

local f
f = function() end

-- not

local f = function() end

the difference between the last two examples is important: the local variable still doesn’t exist to the right of the = that gives it the initial value. So if the contents of the function used f to get a reference to itself, it will correctly get the local variable in the first and second versions, but the third version will get the global f (which will be nil, if not a completely unrelated value set by some other code).

Closures

Functions can use local variables created outside of them. These are called upvalues. A function that uses upvalues is called a closure:

local x = 5

local function f() -- we use the "local function" syntax here, but that's just for good practice, the example will work without it
  print(x)
end

f() --> 5
x = 6
f() --> 6

The function sees the change even if it’s changed outside of the function. This means that the variable in the function is not a copy, it’s shared with the outer scope.

Also, even if the outer scope has passed, the function will still hold on to the variable. If there were two functions created in the scope, they will still share the variable after the outer scope is gone.

local function f()
  local v = 0
  local function get()
    return v
  end
  local function set(new_v)
    v = new_v
  end
  return {get=get, set=set}
end

local t, u = f(), f()
print(t.get()) --> 0
print(u.get()) --> 0
t.set(5)
u.set(6)
print(t.get()) --> 5
print(u.get()) --> 6

Since the two values returned by the two calls to f are independent, we can see that every time a function is called, it creates a new scope with new variables.

Similarly, loops create a new scope on each iteration:

local t = {}

for i = 1, 10 do
  t[i] = function() print(i) end
end

t[1]() --> 1
t[8]() --> 8

Why are local variables difficult in the interactive interpreter

Because it runs each line in a new scope:

> local a=5; print(a)
5
> print(a) -- a is out of scope now, so global a is used
nil

One thing you can do is wrap the code in a do-end block, but it won’t be interactive until you finish writing the whole block:

> do
>>  local a = 5
>>  print(a) -- works on a new line
>> end
5

Why not local by default?

You might be coming from another language that makes variables local by default, and are probably thinking “what is the point of all this extra complication? Why not make variables local by default?”:

x = 3

-- more code, you might have even forgotten about variable x by now...

function ()
  -- ...
  x = 5 -- does this create a new local x, or does it change the outer one?
  -- ...
end

-- some more code...

The problem with changing the outer one is that you might have intended to make a new variable, and instead change the existing one that you might not even know about, introducing bugs.

The problem with creating a new one is what if you actually want to change the outer one?

With the local keyword, it’s all explicit: without local, you change the existing variable, with it, you create a new one.

For more discussion about this, see LocalByDefault.

When to use local variables

The general rule is to always use local variables, unless it’s necessary for every part of your program to be able to access the variable (which is very rare).

Since it’s easy to forget a local, and since Lua doesn’t warn you about it (instead silently creating a global), it can be a source of bugs. One solution is to use a script like strict.lua (shown below), that uses metatables (mentioned in a later tutorial) to trap global variable creation and raise an error. You can put the script in a file in your project, and do require(“strict”) to use it.

--
-- strict.lua
-- checks uses of undeclared global variables
-- All global variables must be 'declared' through a regular assignment
-- (even assigning nil will do) in a main chunk before being used
-- anywhere or assigned to inside a function.
--

local mt = getmetatable(_G)
if mt == nil then
  mt = {}
  setmetatable(_G, mt)
end

__STRICT = true
mt.__declared = {}

mt.__newindex = function (t, n, v)
  if __STRICT and not mt.__declared[n] then
    local w = debug.getinfo(2, "S").what
    if w ~= "main" and w ~= "C" then
      error("assign to undeclared variable '"..n.."'", 2)
    end
    mt.__declared[n] = true
  end
  rawset(t, n, v)
end
  
mt.__index = function (t, n)
  if not mt.__declared[n] and debug.getinfo(2, "S").what ~= "C" then
    error("variable '"..n.."' is not declared", 2)
  end
  return rawget(t, n)
end

function global(...)
   for _, v in ipairs{...} do mt.__declared[v] = true end
end

For more info about enforcing use of local variables, see DetectingUndefinedVariables. RecentChanges · preferences edit · history Last edited December 21, 2013 9:07 am GMT (diff)