3

I understand the difference between the scope of local and global variables, but I am confused about the difference and effects of declaring a variable OUTSIDE of Lua functions (or LOVE2D core callback functions). Consider these three code snippets:

  1. declare function as global, outside of all functions

    var = 30  
    
     function f1()
         --
     end
    
     function f2()
         print(var)
     end
    
     f1()
     f2()
    
  2. declare function as local, outside of all functions

local var = 30  

function f1()
    --
end
    
function f2()
    print(var)
end
    
f1()
f2()
  1. declare function as global, inside of a function
function f1()
    var = 30
end
    
function f2()
    print(var)
end
    
f1()
f2()

All three of these have the same result. I know that all global variables are stored in the global environment table (accessible via _G), and that official Lua documentation discourages the use of global variables:

It is good programming style to use local variables whenever possible. Local variables help you avoid cluttering the global environment with unnecessary names. Moreover, the access to local variables is faster than to global ones. (from https://www.lua.org/pil/4.2.html)

So, what is the difference between these three and which is the best one to use?

I am specifically looking at LOVE2D, although their core callback functions act like normal functions. I guess my question is, if I want to declare a Player (table), where should I put Player = {} (either at the top of main.lua or inside love.load) and if I should declare it 'local' (if at the top of main.lua)?

3 Answers 3

3

One good reason to declare a variable local outside of function scope is that your file is used as a module, and you don't want to pollute the global variables table with unnecessary entries. for example:

-- file: foo.lua

local function hello()
    return 'Hello World'
end

local self = {
    ['hello'] = hello
}

return self

Note that both self and hello are local symbols, so they don't overwrite any existing global variables.

-- file: main.lua
self = 'Lorem Ipsum'
local hello = require('foo')

print(hello.hello())
print(self) -- this prints the global self, 'Lorem Ipsum'

Now, if self in foo.lua was local, the last line wouldn't print a string, but the table defined in foo.lua. But instead, we get the output we expect:

$ lua main.lua
Hello World
Lorem Ipsum
Sign up to request clarification or add additional context in comments.

Comments

3

As a more general programming practice, you should always be aiming to limit variables to the least visible scope possible. Not just in a global vs. non-global sense, but in all scopes (e.g., if a variable only needs to be visible to a certain block, make it so). This helps manage memory (by avoiding leaving references hanging around longer than needed) and eliminates a whole host of problems associated with naming conflicts. This practice makes debugging much easier.

Locals are (see: should be) faster in Lua, but you would have to profile that to see if it is meaningful to you.


Aside from providing access to program arguments, love.load is a context in which you can be absolutely certain other love.* functions and properties will work (i.e., the module will be fully loaded).

Note: You can see the default calling order of the callback functions in love.run (the main callback function).

If the data you are initializing does not rely on the love module context, there is no need for it to be done so in the love.load callback.

Best practices would be to use local as often as possible, and initialize data where it is most appropriate to do so.

--[[
`player` is local to our file, it does not need to be visible outside this scope
it contains a couple of integers right now, these don't rely on `love` in any way
so can be safely initialized right here
]]
local player = {
    health = 100,
    gold = 0,
}

function love.load(args)
    --[[ here we can be certain the `window` and `graphics` modules are up and running ]]
    local w, h = love.window.getMode()
    player.sprite = love.graphics.newImage('sprite.jpg')
    player.position = { x = w / 2, y = h / 2 }
end

function love.draw()
    love.graphics.draw(player.sprite, player.position.x, player.position.y)
    love.graphics.print(('health: %d, gold: %d'):format(player.health, player.gold),
        player.position.x, player.position.y + 65)
end

This last point is difficult, as the most appropriate place to initialize data will change with the structure of your program. You will find that the cursory example above only remains relevant for so long before this structure of program becomes unwieldy. You'll almost certainly gravitate towards a more object-oriented style, where the entities in your game become instances of classes, with their own initializers, the definitions of which live in their own files, and are acquired with require.

Once you have multiple files, it becomes even more important to not pollute the global namespace.

Comments

0

To clarify a bit the "confusing" case of declaring local variable outside of function - when you execute any lua source it gets compiled as an anonymous chunk, a body of the function that is executed and discarded immediately after the source is compiled. The local variable outside of the functions declared within the source is local to the anonymous chunk being compiled. And for any function declared within this source that 'local' variable is an upvalue - a variable captured in the function's lexical closure.

The resulting identical behavior is only the same in your particular case, since you don't care if the variable with the same name already exists in the global environment or not. The scoping difference for that variable between the cases is quite substantial.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.