Scope in Lua can be a bit of a challenge if you’re used to how scope works in other languages, such as AS3. Rather than try to write the exhaustive, authoritative description of scope in Lua, I figured I’d do a quick overview to explain a few things that may not be obvious when you’re starting out, and also describe how I typically handle potential scoping issues.
Local v. Global
First off, the difference between local variables and global variables. The basic difference is pretty simple: anything that is not explicitly declared as a local variable is automatically made global. You don’t need to explicitly make a variable global by using _G, nor do you have to declare globals in a specific location. So, for example:
MyModule.lua:
1 2 3 4 5 | |
main.lua:
1 2 3 4 | |
If you’re coming to Lua from another language, you have a few options for how to handle things:
Avoid using global variables at all. Probably a comfortable approach if you’re coming from a language like AS3, where globals are allowed but not really encouraged as a best practice.
Use globals, but restrict their definition to main.lua. Optionally also use
_Gwhen declaring and accessing global variables (e.g._G.foo = "foo") to make it clear that this variable is global on purpose.Put all your globals into a single module whose sole purpose is to store global values.
Which one do you choose? That’s entirely up to you. I usually go with the third approach, since it forces me to do a little extra work to create a global variable, which helps make sure that I actually think through whether some needs to be global before I make it so. But I’ve also gone with option two on occasion, and it works just fine.
Local Scope
Local variables are local to the enclosing chunk. Chunks are basically one of four things:
The enclosing module (i.e. the
.luafile);The enclosing function
The enclosing control structure
do/endchunks
So, consider the following simple module:
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 | |
When called, Module.new() will return a new display object. When touched, the object’s x position will be incremented by a random amount from 1 to 5. The amount is stored in a local variable, xDelta, and the actual position is stored in a different local variable, xPos. The callback also tracks the number of times the object has been touched.
So, there are three different local variables:
counter: declared at the module level, this variable is accessible throughout the entire module. Since it’s declared at the module level, all instances created withModule.new()will refer to the samecountervariable. This is basically how you would implement static methods or properties in LuaxPos: declared inside thenew()function, this is accessible only withinnew(). When the statementprint("xPos", xPos)is executed, it will return a value ofnil, becausexPosisn’t accessible outside of its scope.xDelta: declared inside theifcontrol block, this value is accessible only within theifblock. As a result, the statementprint("xDelta", xDelta)will return a value ofnil. However, the value ofcounterwill be properly printed, becausecounteris accessible throughout the module.
(In both cases, the value returned is nil because Lua is looking for a global variable with that name since it hasn’t found any locals. Undeclared globals have a value of nil.)
Other control blocks for the purpose of local variables:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | |
The trickiest part in these examples is that in a for statement, the iterator variable is automatically declared as a local variable. This means that the value of i in the example above is unavailable outside of the loop. So, if you want to iterate over something, potentially breaking out of the loop when some condition is reached, and you need to remember the point at which you broke out of the loop, you need to declare a local variable outside of the for loop, and set its value equal to the iterator before your break.
Scope in Functions
Another aspect of scope in Lua is when dealing with functions. Unlike other languages, with many Lua functions you need to explicitly declare the scope of a function call. You can see this throughout the table and string packages:
1 2 3 4 5 | |
In effect, the first argument being passed to these functions is scope of the function call. It’s telling table to insert or remove values from myTable, or to do stuff with myString.
This is done so frequently that in many cases Lua provides “syntactic sugar” to make it easier to make the same function calls. It doesn’t apply to the table package, but for the string methods above they could be rewritten this way:
1 2 | |
In effect, using a colon to call these methods lets you emulate the sort of object-oriented approach that you get in other languages. With the colon, Lua knows to pass the object as the first parameter of the function.
You can recreate this same approach in your own code in one of two ways: either declare your function using a colon, or make self the first parameter of the function:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | |
The output from this will be:
1 2 3 4 | |
Since the value of self from both functions matches the value of g from the original print statement, you can see that whether done implicitly or explicitly, calling a method using the colon will correctly set the proper scope. However, in the last case, where the function was called using dot notation, no reference to self was passed to the function, so self is nil.
One implication of this approach is that in Lua the scope of a method of an object can be set to any other object. Like this:
1 2 3 4 5 6 7 8 9 10 11 12 | |
This gives us the following output:
1 2 3 4 | |
So even though test2 is a method of the display object g, it can act on h if we pass it to that function. Not that you’d want to do that.
Specifically in the Corona APIs, you see the colon notation fairly frequently:
1 2 3 4 5 6 7 8 9 10 | |
All That Said…
I never (okay, very rarely) actually use self in Corona apps. About the only time I’ll use it is if I’m creating a bunch of ‘anonymous’ objects - i.e. objects for which I’m not explicitly storing a reference - and need to create some kind of event handler for them. In that case, I’ll typically use a table listener rather than a function listener:
1 2 3 4 5 6 7 8 9 10 11 12 | |
(Even though in this case you could also use event.target to figure out what object was touched.)
However, the far more common approach I’ll take is not not use self at all and simply rely on the closure to take care of scope for me. So for example, here’s a condensed version of the module we looked at earlier:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | |
Notice that inside the onTouch event handler, I’m referencing the display object g directly, rather than using self. Because Lua supports closures, even though g is a local variable (and so the reference to it should be removed when the function has finished execution), it remains in scope as long as it’s required for some other chunk of code to run.
(That may not be the most elegant way to describe closures, but should get the point across.)
The added benefit of this approach is that it doesn’t really matter whether you use colon or dot notation to access your object’s methods. I remember reading one comment where the developer said they prefer to use colon notation to reinforce the fact that they’re calling methods of an instance of a class, rather than a class/static method. My current inclination is to use the AS3-style naming conventions to distinguish between classes and instances of a class (so, Class.new() v. instance.doStuff()). But as with some many things in Lua: whatever floats your boat.