Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Declaring a variable as 'local' when outside Lua functions

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)?

like image 407
Garth Elliott Avatar asked Dec 22 '25 13:12

Garth Elliott


2 Answers

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
like image 142
Enno Avatar answered Dec 24 '25 11:12

Enno


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.

like image 42
Oka Avatar answered Dec 24 '25 11:12

Oka



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!