lua-users wiki: Scope Tutorial
Scope Tutorial
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
(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
(a)
--> 5
do
local
a = 6
-- create a new local inside the do block instead of changing the existing a
(a)
--> 6
end
(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()
(x)
--> nil
local
x = 6
(x)
--> 6
end
function
foo()
local
x = 5
(x)
--> 5
bar()
(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 =
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
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
(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
end
local
function
set(new_v)
v = new_v
end
return
{get=get, set=set}
end
local
t, u = f(), f()
(t.get())
--> 0
(u.get())
--> 0
t.set(5)
u.set(6)
(t.get())
--> 5
(u.get())
--> 6
Since the two values returned by the two calls to
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
()
(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;
(a)
(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
>>
(a)
-- works on a new line
>>
end
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 4:07 am GMT
(diff)