Best practice - Declarations and assignments on global and local scopes. Pros and cons?

Some programs usually recommend not using global variables and declarations, but rather only use them in local scopes. Is there recommended approach to these?

Should it be limited to globally declare variables, but not assigning values to them and do the necessary things inside procedures to affect those global variables?

There’s no reason to avoid global variables as long as you know how you are using them.

  • If you only have a single thread, it’s perfectly fine.
  • If you have multiple threads but you use some form of synchronisation (mutex, atomics) they are also fine.
  • If you have multiple threads but the data is only ever read, they are fine as well.

And here’s the kicker… even if you scope them, you should still know how you are using them. Just because you scope e.g. a [dynamic]T it doesn’t become thread safe. Actually, if you keep passing around a ^[dynamic]T it would even become worse if it goes out of scope and you delete the array it points to.

Regarding initialization, if you know everything at compile time I’d just write it out if the compiler allows it, otherwise use an @(init) proc to populate the data on startup (of course assuming you can init it before the runtime even calls main ).

In general, it’s just bad if you init globals from all sorts of different procs at runtime. If you ever need to move code around, you can easily break assumptions on when a global can be accessed.

2 Likes

Hmmm… So far I don’t think I have come across something that would generally need global variable. Probably some structs that work as passing data from one procedure to another. X11 window, surface and Vulkan/OpenGL instancing can be handled in main procedure scope.

The first thing that comes to mind is static look up tables for e.g. stream decoding/decompression. :slightly_smiling_face:
You don’t necessarily want to have a large table instantiated on the stack of many-many threads.

I make a lot of stuff global so I don’t have to pass many parameters around all of the time. Things like:

  • A bunch of structs with Vulkan handles and data
  • The current loaded level
  • The list of all “things” in the current level
  • If the game is running in full screen mode
  • Some variables with things like which buttons are held down on the controller
  • Lists of debug objects like bounding boxes to render to the screen

You don’t have to make anything global if you don’t want to. But don’t let the OO people bully you into thinking it’s “best practice” to pass loads of variables around all over the place. It’s up to you.

Also just imagine making one giant struct called “Stuff” and having all of the above things in it, and passing a pointer to that around all over the place. There would be no global variables. Does that mean it would be “cleaner”?

And by the way I absolutely detest the term “Best Practice”. It’s a phrase that people use to imply that their opinions are actually facts. Do whatever you want - you are an adult!

I find that the “wisdom” that global variables are bad comes from much the same mindset as the immutability lovers. The goal in both cases is to avoid spooky action at a distance because you don’t know who’s modifying what when. But like zen3ger says, you can still get spooky action at a distance from scoped variables, so avoiding globals isn’t necessarily going to help you out.

Having scoped variables rather than globals tends to make it a bit harder to modify things at unpredictable times and leave something in a bad state for another part of the codebase. That’s why they say that global variables are bad. Generally it is good advice, but it isn’t gospel. When you find that there’s only ever one of something and you always need that same thing, a global can really help you out. It isn’t global to avoid passing it around as a parameter; rather it’s global because it’s the only one you could ever want, and you need to access it from a lot of different places.

I find that the concept of “best practices” is really more like guidelines that are a good idea to adhere to until you truly understand their purpose. Many engineers treat them as gospel and take a dogmatic approach to them, but that’s actually worse than breaking the rules at the right time.

Globals are a weird debate. It’s obviously less work to just read/write something rather than pass it in the parameters. In the context of Odin? Odin’s calling convention makes possible to think of procedure parameters in terms of whether they need to be written or not. i.e. Use a pointer if you need to write that value, not for speed. When this is the assumption while reading code, one should even bother to pass pointers to global memory in the procedure parameters, rather than just writing the global. It makes the code more uniform and easier to follow, as well as serving as a decent mitigation to the potential pitfalls of using globals.

So my approach to this, in a slogan, is to read globals but write parameters. Though I still write globals without passing them as parameters in various cases.

Also, globals can increase clarity. When data is part of the procedure parameters, there may even be the implication that the data can arrive from many sources-- when in fact there’s just one instance of the data. A global can be precise.