Search code examples
luafivem

global const variables in lua 5.4


i know you're able to do constant variables in lua now with <const>, but they seem to only work with local variables:

local myNumber <const> = 10 -- This works perfectly fine

myGlobalNumber <const> = 10 -- This causes an error

Is it possible to have constant global variables as well?

myGlobalNumber <const> = 10


Solution

  • The point of the <const> local variable attribute is to allow Lua to check, at "load" time (when you call dofile / load / require or similar), whether a local variable is not being mutated - that is, whether the only assignment is the initial one at declaration time. It is a tool to help programmers (1) express intent (2) get slightly more "load time" checking than just syntactic checks.

    This works for local variables precisely because they are local. They are visible only within function scope. This means Lua has all the information it needs when you load a "chunk" of code (effectively a function body) to check whether it abides by the static (load time) rules of <const>.

    "Globals" / "environmental variables" (as I like to call them) are effectively just syntactic sugar for accessing fields in the _ENV (usually _G) table. As such, they are much more dynamic than local variables. You could do _G[some .. complex .. expression] = 42, for example. With global variables, just as with any other table fields, Lua does not have all the information it needs at load time. To begin, it does not even know which fields your code will set or get. Even if it knew this, or limited itself to the <name> syntax, that would still be insufficient. Consider loading a file where a certain global variable that was not set yet is used (perhaps in some callback where the author knows it will be available in time). Lua can not warn about this at the time the file is loaded. (It could, at best, try to warn when the file containing the conflicting constant definitions was loaded, then warn when subsequent files are loaded.)

    As ESkri has said, you can implement run-time checks to guard against accessing constant global variables via a metatable. That could look something like this:

    local constants = {pi = math.pi}
    setmetatable(_G, {__index = constants, __newindex = function(_, key) error("attempt to change constant global variable " .. key) end})
    print(pi) -- runs fine
    pi = 3 -- errors
    

    However, this is probably a bad idea:

    • It's likely to confuse (future) you as well as whatever static analysis tools you may choose to use. (This could be alleviated to an extent using a different implementation however.)
    • It incurs a performance penalty.
    • It's still only a runtime check. If you don't hit a code path in your testing, you don't get an error or warning.

    Instead, I would recommend the use of static analysis tools such as Luacheck, which lets you specify [read-only global variables in its configuration file, and also supports a new read globals inline option to add read-only global variables, similar to a "constant" definition. Example:

    -- luacheck: globals pi
    pi = math.pi
    -- luacheck: new read globals pi
    pi = 3
    

    it should be noted that while options are limited in scope to files, the read_global configuration field is per-"project" (folder) you're running Luacheck in. To establish pi as a read-only global project-wide, you could set read_globals = {"pi"}, then use -- luacheck: globals pi to make an exception where pi is defined (followed by -- luacheck: new read globals pi to revoke that exception afterwards).