Ludicrous Software

Scope in Corona

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
local MyModule = {}

foo = "foobar"

return MyModule

main.lua:

1
2
3
4
local MyModule = require("MyModule")

-- outputs 'foobar' to the console
print(foo)

If you’re coming to Lua from another language, you have a few options for how to handle things:

  1. 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.

  2. Use globals, but restrict their definition to main.lua. Optionally also use _G when declaring and accessing global variables (e.g. _G.foo = "foo") to make it clear that this variable is global on purpose.

  3. 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:

  1. The enclosing module (i.e. the .lua file);

  2. The enclosing function

  3. The enclosing control structure

  4. do/end chunks

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
local Module = {}

local counter = 0

function Module.new()
  
  local g = display.newImage("myImage.png", 0, 0)
  
  local xPos = 0
  
  local function onTouch(event)
      if event.phase == "ended" then
          local xDelta = math.random(5)
          xPos = xPos + xDelta
          g.x = xPos
          counter = counter + 1
      end
      print("xDelta", xDelta)
      print("touches", counter)
  end
  
  g:addEventListener("touch", onTouch)
  
  return g
  
end

print("xPos", xPos)

return Module

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 with Module.new() will refer to the same counter variable. This is basically how you would implement static methods or properties in Lua

  • xPos: declared inside the new() function, this is accessible only within new(). When the statement print("xPos", xPos) is executed, it will return a value of nil, because xPos isn’t accessible outside of its scope.

  • xDelta: declared inside the if control block, this value is accessible only within the if block. As a result, the statement print("xDelta", xDelta) will return a value of nil. However, the value of counter will be properly printed, because counter is 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
while foo < bar do
  local q = 5
end

repeat
  local j = 10
until foo < bar

for i = 1, 10 do
  local k = 15
end

do
  local i = 5
end

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
table.insert(myTable, "foo")
table.remove(myTable, 5)
local length = string.len(myString)
myString = string.lower(myString)
-- etc.

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
local length = myString:len()
myString = myString:lower()

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
local g = display.newRect( 0, 0, 100, 100 )

print("g", g)

function g:test()
  print("g:test", self)
end

function g.test2(self)
  print("g.test2", self)
end

g:test()
g:test2()
g.test2()

The output from this will be:

1
2
3
4
g       table: 0x5bdaf0
g:test  table: 0x5bdaf0
g.test2 table: 0x5bdaf0
g.test2 nil

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
local g = display.newRect( 0, 0, 100, 100 )
local h = display.newRect( 0, 200, 100, 100 )

print("g", g)
print("h", h)

function g:test()
  print("g.test", self)
end

g:test()
g.test(h)

This gives us the following output:

1
2
3
4
g       table: 0x202d490
h       table: 0x202d5e0
g.test  table: 0x202d490
g.test  table: 0x202d5e0

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
local g = display.newImage("image.png")

g:removeSelf()

-- is the same as:
g.removeSelf(g)

-- so yes, you could also do:
g.removeSelf(h)
-- but again, why would you want to?

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
local function onTouch(self, event)
  if event.phase == "ended" then
      print(self)
  end
end

for i = 1, 6 do
  local rect = display.newRect( i * 15, i * 15, 10, 10 )
  rect.touch = onTouch
  rect:addEventListener("touch", rect)

end

(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
local Module = {}

function Module.new()
  
  local g = display.newImage("myImage.png", 0, 0)
  
  local xPos = 0
  
  local function onTouch(event)
      if event.phase == "ended" then
          local xDelta = math.random(5)
          xPos = xPos + xDelta
          g.x = xPos
      end
  end
  
  g:addEventListener("touch", onTouch)
  
  return g
  
end

return Module

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.