Scope Tutorial

lua-users home
wiki

Variable scopes

Programs are broken up into units of code, like functions and modules. Within these units we may create variables to hold values so that we can process data and make the program perform a given task. For a number of reasons (e.g. potential name clashes, to hide information etc.) we may want to hide variables in one unit from another. We may also want to create temporary variables for a task which can be removed once we are finished with them. The term "unit" is a little vague. We use the term scope to describe the area in which a set of variables live.

Variables that we have access to are said to be visible. The scope of a variable is the containing block of code in which it is visible. Scopes are created and destroyed as the program executes and passes in and out of blocks. The variables contained within these blocks are created and destroyed according to the rules described on this page. When entering a block a new scope is created (the inner scope) and switched to. Outer scopes are visible from inner scopes but not vice versa.

In Lua blocks of code can be defined using functions and the do...end keywords. e.g.,

> function foo() local x=1 end
> foo()
> do local y=1 end
The above example just defines a function called foo which contains a scope. We create the variable x in the inner function scope. When we exit the function, the scope ends, the variable x is deleted and is no longer visible. The do...end block contains similar functionality.

Global scope

Any variable not defined as local is in the global scope. Anything in the global scope is accessible by all inner scopes.

> g = "global"
> print(g)
global
> function foo() print(g) end
> foo()
global
In the above example g is in the global scope. The function foo is also in the global scope. We enter the foo function scope when foo() is called. We can print the value of g because we can see the outer scope from the inner foo scope.

The local keyword

We use the keyword local to describe any variables which we would like to keep local to the scope they are defined in.

Note: All variables declared in Lua default to the global scope unless they are specified local, or are parameters within a function. This may not be the exact behaviour you are used to: In C and many other languages, variables declared in inner scopes are local to that inner scope by default.

> a = 1                      -- global scope
> function foo()             -- start of foo scope
>>  b = 2                    -- no local keyword so global scope
>>  local c = 3              -- local scope
>> end                       -- end of foo scope
> print(a,b,c)               -- before we call foo
1       nil     nil
> foo()
> print(a,b,c)               -- after foo called
1       2       nil

Local scope

When we create a block we are creating a scope in which variables can live. e.g.

> do local seven = 7 print(seven) end
7
> print(seven)
nil
In the above example do and end enclose a block containing the declaration of the local variable seven. You can see that we can print its value, 7. When we exit the scope at the end keyword we lose visibility of the local variables in that scope. When we print the value of seven once outside the scope we get nil. This means "variable not found". The keyword local is placed before any variable that we want to remain visible only to that scope and its inner scopes.

In the following example x starts with the value 1. We create a block using the do and end keywords. We use the local keyword to specify that we want a new variable also called x, which is only visible in this block or scope.

> x = 1
> print(x)
1
> do
>>  local x
>>  x = 2
>>  print(x)
>> end
2
> print(x)
1
You can see that once the do...end scope has ended the second declaration of x disappears and we revert back to the old one.

> x,y = 1,2
> print(x,y)
1	2
> function foo(x)
>>  x = 3
>>  print(x)
>> end
> foo(y)
3
> print(x,y)
1	2
The same thing happens with function parameters like x, since they are local to the function. Although not a matter of scope, the y argument didn't change since foo is only passed a copy. However, the situation is more complicated with tables. Parameter names are still within a local scope, but they can refer to tables in an outer scope.

> t = { a="apple" }
> print(t.a)
apple
> function foo(x)
>>  x.a = "orange"
>>  print(x.a)
>> end
> foo(t)
orange
> print(t.a)
orange
If you pass t to a function which then changes its contents, the change happens to t. This is because the function is passed a reference to t, not a unique copy of t, meaning that t and x refer to the same table. Changes to x persist in the outer scope, accessible through t.

> t = { a="apple" }
> print(t.a)
apple
> function bar(x)
>>  x = {}
>>  x.a = "orange"
>>  print(x.a)
>> end
> bar(t)
orange
> print(t.a)
apple
Assigning x to something else does not modify the table that t refers to. This is because the local reference to t is merely replaced by another. Any further changes to the new object do not affect t.

local in the global scope

The local keyword can be used in any scope, not just inner and function scopes. This might seem a little unintuitive but even the global scope in Lua can become a inner scope if it is used as a module.

Because of the implementation of Lua it is more efficient to use local variables wherever possible. The technical reason for this is that local variables are referenced via an assigned number, whereas global variables are stored in a table which is accessed with a key (the variable name). Table lookups are very fast in Lua, but still not as fast as local register lookups.

If the following code is compiled we can look at the Lua virtual machine instructions outputted.

g = "global"
local l = "local"
print(g,l)

The command line tool luac (Lua compiler) can be used to compile the code, which gives:

main <1.lua:0,0> (8 instructions, 32 bytes at 0x671420)
0+ params, 4 slots, 0 upvalues, 1 local, 4 constants, 0 functions
        1       [1]     LOADK           0 -2    ; "global"
        2       [1]     SETGLOBAL       0 -1    ; g
        3       [2]     LOADK           0 -3    ; "local"
        4       [3]     GETGLOBAL       1 -4    ; print
        5       [3]     GETGLOBAL       2 -1    ; g
        6       [3]     MOVE            3 0
        7       [3]     CALL            1 3 1
        8       [3]     RETURN          0 1

We won't go into great detail about what is going on here but you can see that the variables g and l are treated differently, with access to locals being more immediate. You can find more details in the OptimisationTutorial?.


RecentChanges · preferences
edit · history
Last edited July 30, 2012 8:47 pm GMT (diff)