Understanding the Global Environment and _G

Nowadays, Lua it-self has the local and global environment. We will be just talking about the global one. Therefore… actually Lua keeps the access to this global variables within a hash table (dictionaries) within it’s name and value. This is actually already pretty efficient, but stills, really really slower than just accessing the stack (the one holding the local ones). Anyway, take in consideration that basically Lua does not just keep them within a hash table, but it-self, it actually keep them in multiple environments to be more precise. However, we will ignore that in this section for the moment.

Anyway, there’s a lot of efficiency when it comes to this global access. One of them it’s that it simplifies it it-self since it doesn’t require further data structures for saving the global variables and that’s already really cool.

But why we said on the title _G too? well, this “table” it’s actually giving you the advantage to manipulate the values inside of it, where the global ones are actually stored.

How this works? basically Lua will store the environment it-self in a global variable called _G global, now this is the one that let us get all of that. anyway, something interesting is that this is really similar to cyclic referencing , since _G._G it’s the same LOL (because _G it is where stored the global(s)).

just some example code:

for _, global in pairs(_G) do print(global) end

(This is a pretty simple code and it’s actually on the wiki it-self. It’s a pretty good example to explain how this works) so basically here we will print any global access that basically, it is stored in the _G environment, cool right?

anyway, now something interesting is that… just doing this:

foo = ... -- some value

It will get up the variable in the heap or the global environment (just as basic as that!) then we can later on access it with _G.foo and it will get the value it-self! now. Something even more interesting is that we can actually do meta-programming within this environment! This is because _G it’s just a normal table, and Lua will treat it as that.

Therefore… we do as this for example:

local t = setmetatable(_G, {
    __call = function(self, ...)
        ... -- code
    end
})

_G() -- it will be alright!

Yessir, this is completely right and completely valid, so that’s just some basic go thru over the _G hash table and so on!

Thanks for reading!

(This post was inspired by @Lil_SharkyBoy)

12 Likes

You might want to add a disclaimer that this doesn’t work on roblox since _g is functionally the same as shared

Also, as I learned today, Roblox’s environment metatable is locked and can’t be assigned or modified.

3 Likes

I mean it’s nice and all but explaining _G is as simple as saying that it’s a reference to a table that’s shared between all scripts (one memory address containing a table, _G points to it).

This is an awfully late time to be writing this tutorial though. In 2021 I do not encourage anyone to use _G because there’s no legitimate use case for doing so. Additionally, most codebases are going to be well structured and modular enough that you can just pass variables around between scripts directly.

ModuleScripts cover every use case _G has. Use ModuleScripts, never use _G. Avoid using non-Roblox globals and architecture your games properly.

8 Likes

This is a really great post, very nice.
Now I’m gonna go plant some trees, to protect my global environment.

This isn’t good advice and commonly leads to poor code architecture. If you want to discuss this on a technical level, it does matter a fair bit.

Non-Roblox global variables are never good to use in Roblox code (worse if you insert them via setfenv, as it disables Luau optimisations). ModuleScripts are a lot more flexible when it comes to structuring your code and displaying clear separation between code. ModuleScripts cover every use case that _G can; there is never a case where one is better than the other.

Local variables are nice and quick to access on the stack. Additionally those variables, assuming you don’t hold strong references to them, clean themselves up when the scope closes. Develop good habits by cleaning up variables, whether explicitly or implicitly, that you no longer require.

Never use _G. It is not helpful to any use case except resolving backwards compatibility issues that you can’t fully tackle now but intend to down the line. No new project in 2021 ever has a genuine reason to use _G. Whatever use case you’re thinking of, ModuleScripts do it better.

5 Likes

Yes, locals are recommended over global because of it’s uses. Such as you already mentioned Colbert, if there’s no any reference for it the garbage collector will take care of it, freeing that memory that it isn’t needed for further computations on our program, now, I never encouraged people to use globals over locals, because as we already know the locals are faster since they’re being pushed and popped out from the stack where they’re located, anyway, I did this post with the intention to explain to those who ask them-selves how this works it-self, not how to or they need to use it. Thanks.

1 Like

What about the case where you want to share data between scripts and modules without having to write heaps of boilerplate (e.g. requiring modules and getting services and instances in the game tree)?

_G is able to solve that problem. It may not be a good idea to use _G, for unrelated reasons, but nonetheless it solves the problem in a fashion that is generally simpler (provided some other preconditions are met, like having a well-defined load order).

i.e. compare and contrast:

_G.contextActionService = game:GetService("ContextActionService")

with

-- aggregated from lots of different scripts
local contextActionService = game:GetService("ContextActionService")
local contextActionService = game:GetService("ContextActionService")
local contextActionService = game:GetService("ContextActionService")
local contextActionService = game:GetService("ContextActionService")

To reach this point, you need to invent and maintain a system that is able to do so. Set up module scripts correctly, etc. Again, lots of boilerplate.

Yeah, it’s probably not a good idea to use _G now. But it seems like it leaves a bit of a void, unfortunately.

1 Like

I’d still not recommend using _G in that circumstance. It’s okay to not want to write heaps of boilerplate and you don’t have to either but it depends on the specific case you want to accomplish as well as your general code structure, assuming you have one at all.

Just to talk a bit about the example you provided, I personally have cases like this in several parts of my code (a lot of it has to do with us migrating from a freeformed codebase to a framework). Modules are passed around to each piece of code so this is resolved by containing services in a module.

RobloxServices.lua
This is included as part of each module wrapped with the framework.

return {
    Players = game:GetService("Players")
}

It can be accessed with just a few dots:

self.RobloxServices.Players

So in the realm of sharing data/constants/variables/etc between code, ModuleScripts can still resolve that problem for you without having to use any globals. You could still do this without a framework by just requiring the module as well, though I see where your point comes into play because then you’d need a service to fetch that module in the first place. It’s then worth asking if boilerplate is really that annoying that you’d prefer to use _G instead.

I’d wager that data is a more nuanced case and is heavily dependent on what you need to do, whether simply setting it in a table or passing it around is enough or if you have more processes you need to run on that data before passing it.

Sure though, in my case I explicitly have a framework so I’ve already done the part of inventing and maintaining a structure that allows me to avoid _G. Most developers don’t but in the same vein that I recommend avoiding _G, I encourage having a well-defined and modular structure. Developers don’t necessarily need to learn it immediately considering how open Roblox is but in time it would be a good practice to learn. Might also help wrt upcoming deferred events.

4 Likes

Yeah, I find it a little bit frustrating since I’ve become aware of what it’s like to work in a language like C where you can just #include only what’s relevant in each file and everything “just works” without the need for frameworks or paradigms. That’s kind of the model I’m trying to emulate – but without god scripts or _G, it seems to be impossible.

I’m starting to think it might be a good idea if Roblox added some way to reliably and easily require known modules. Half of our problems seem to come from needing to make access to (often static) modules convenient and reliable. For example being able to give each module an ID that you can just type in without needing to worry about accessing a dynamic tree and needing to use WaitForChild everywhere or whatever. A solution to that problem would be quite impactful, in a good way I think.

1 Like

This is actually already pretty efficient, but stills, really really slower than just accessing the stack

No, hash table lookup times are O(1) in the average case and O(n) in the worst case due to hash collisions. Hence, for the most part hash table lookup times are O(1). Also note that the new Luau’s VM is heavily optimized and the performance difference is negligible.

It will get up the variable in the heap or the global environment (just as basic as that!) then we can later on access it with _G.foo and it will get the value it-self! now . Something even more interesting is that we can actually do meta-programming within this environment! This is because _G it’s just a normal table, and Lua will treat it as that.
Therefore… we do as this for example:

That is incorrect - declaring and initializing a global variable doesn’t append it to _G. _G and the script’s global enviroment are different.

e = 1
print(_G.e) 
print(_G == getfenv()) -- the script's internal function's environment 
-- (the internal "main" function in which - this code is ran)
print(_G == getfenv(0)) -- the script's thread enviroment

--> nil
--> false
--> false

2 Likes

i have a pretty good use case for _G firstly its tht passing around the data doesnt work once it gets more and more complex i have 15 module scripts accessing the same data and passing it around is an unneccessary headache.

is there any proved disadvantage to _G if so ima shift to the shared global instead if not then uuh cool ig

That’s not a good use for _G, you can still use ModuleScripts for that if you have a good system set up to send and receive that data or work on centralising your implementation. I have a codebase consisting of over 350 ModuleScripts and this is just files that are synced in via Rojo; one system alone is already worth 500 unsynced ModuleScripts.

Complex data is still passable with ModuleScripts. I am capable of passing a complex data set that can range anywhere from a few one or ten thousand characters without any issue and this is on a live environment, so I can’t see a strong point in using _G simply because you have more complex data and it certainly does work; if it doesn’t, it may be a sign that your structuring is weak.

Regarding “proved” disadvantages, yeah they’ve been talked around in the thread, it chalks up to about the same reason why using globals is highly discouraged. You should keep all your variables local to the current scope and manage them correctly. ModuleScripts allow you to build a strong codebase and pass and hold data already. _G is archaic and shouldn’t be used for new work.

4 Likes