Scope for Beginners

As a new — or relatively new — Lua/Solar2D programmer, are you confused by console errors like this?

Runtime error /Users/yourname/Projects/AwesomeGame\menu.lua:4: attempt to index global 'button' (a nil value)
stack traceback:
    [C]: in function 'error'
    ?: in function 'gotoScene'
    /Users/yourname/Projects/AwesomeGame\main.lua:16: in main chunk

If so, then you need to learn a very important programming concept known as scope. Scope impacts Lua’s ability to “see” variables (as in the error above) as well your ability to manage where functions exist in relation to other functions and variables.

Scope Defined

When you think of terms like “telescope” or “microscope,” you think of devices that help you see, and see things within a limit — for instance, very small things in a microscope. In programming terms, scope is used to define what parts of your code can “see” variables, objects, and functions that you have created. In Lua, there are two main scope controls: global and local.

Global

Global variables/functions can be seen anywhere in your program. “Great, I’ll use global scope for everything!” you might say, but global objects can actually be quite bad! In fact, new programmers should avoid using global variables.

One reason why globals are dangerous is that — because they have no visibility limits — they can be expensive to use in regards to performance. In fact, if a global variable is accessed in a time-critical loop, it may perform as much as 30% worse than a local variable in the same loop.

Global variables/functions also have the inherent risk of being accidentally re-assigned. For instance, if you create a global variable named _W in one Lua file and assign it a value of 320, you may accidentally create another global variable _W in another file, assign it a different value, and effectively “trash” the first declaration. This could obviously have unexpected consequences in the first file which expects _W to have a value of 320!

The global scope is discussed in more detail in the Goodbye Globals! tutorial, along with a convenient method to create/access pseudo-global variables and functions.

Local

In Lua, the preferred scope is local which you control by using the local declaration before you define a variable/function. For example:

local someVariable
local function someFunction()

end

When you prefix a variable or function with local when you first create it, it becomes available to that block of code and any “children” blocks contained within. This concept is highlighted by the following two examples:

  1. Consider this instance:
local function addTwoNumbers( number1, number2 )

    -- Create variable "sum" local only to this "addTwoNumbers()" function
    local sum = number1 + number2
    print( sum )  -- This works!
end

print( sum )  -- This does NOT work (prints "nil" meaning it's unknown to Lua)

In this case, the function addTwoNumbers() defines a new block of code. Inside this block, we create a new variable named sum. This variable has the local keyword, meaning it’s only visible to the function addTwoNumbers() — no other code in this module or overall project will be able to “see” the sum variable. Thus, when the function ends, there is no longer a variable known as sum and nil will be printed to the console.

  1. Now consider this block:
local function addTwoNumbers( number1, number2 )

    local sum = number1 + number2
    if sum < 10 then
        print( sum .. " is less than 10" )
        local secondSum = sum + 10
    end

    print( secondSum )  -- This does NOT work (prints "nil" meaning it's unknown to Lua)
end

Like the first example, sum is local to addTwoNumbers(). That lets us add two numbers, and it’s also visible inside the block started by if sum < 10 then. However, inside of that if-then block, another local variable (secondSum) is created, and that variable becomes local only to the if-then block. So, the attempt to print secondSum outside of the if-then block results in nil because secondSum doesn’t exist at that point in execution.

Index Variables in Loops

Consider a block of code like this:

local i = 999

for i = 1, 10 do
    print( i )  -- prints 1, 2, 3, .. 10
end

print( i )  -- prints 999

Here, a local variable i is set to 999 on line 1. In addition, a loop index variable of i (line 3) is used, but this i is local only to the for loop. As the loop executes, its value represents the values 1-10 and that is reflected by the print( i ) command on line 4. When the loop ends, this i will forget its final value from the loop and return to its previous definition (usually nil). Following that, the local variable i which was declared before the for loop is printed to the console (print( i ) on line 7) with the value of 999. This is because for loops — just like functions — are blocks and they adhere to the same scope rules associated with blocks.

Conclusion

Hopefully, this tutorial has illustrated that scope, as an overall concept, is relatively straightforward. When you use the keyword local, the variable can only be accessed at that block level or within any child blocks. With that in mind, if your variables or functions need to be seen at a wider level, simply move them above/outside the block or define them near the top of the module.