Global Variables are Evil and Unsafe

August 21st, 2022

Global variables are unsafe and evil. You should stop using them.

I believe this so strongly that I think languages should require declaration and access to global varibles to be wrapped in unsafe_and_evil. For example:

// foo.cpp
unsafe_and_evil {
    int g_evil = 0;
}

void IncrementGlobal(int v) {
    unsafe_and_evil {
        g_evil += v;
    }
}

Rust has the unsafe keyword. This isn't strong enough. Mutable global state is both unsafe and evil. Programmers who add global variables are bad and should feel bad.

Why Globals are Evil

This has been talked about alot. I won't list all the reasons. However here's a few points inspired by cases I've recently encountered.

Static initialization order fiasco is a well known C++ footgun. The best fix is "don't do that". Shutdown order is also problematic.

Windows Dynamic Libraries and Linux Shared Libraries behave differently. A Windows .dll is a well encapsulated box with a thin API. A Linux .so defaults to a fat API that exposes implentation details to other libraries. Windows makes it very hard to share global variables across .dlls. Linux enables and even encourages shared global variables. Dealing with this is a pain no matter which platform you prefer. If you don't have global variables it's a non-issue.

Global variables impose constraints. A library that relies on global variables can force you into a singleton. I used pybind11 to embed python in a project. Due to use of global variables I was limited to just a single Python "domain" per process. When a second user wanted a long-running calculation the only option was a second process. (This is distinct from the GIL.)

Global variables enable bad APIs with bad performance. Here's an example where std::locale::classic() locks a mutex everytime it fetches a const&. It caused a 500x slowdown due to contension during multithreaded string comparisons. C/C++ locale is an abomination of bad API design. If it never used global variables it'd be much less bad.

These are just a few reasons. There are many more. Over the past couple of years I have fixed a surprising number of bugs for which the root cause was "global variables".

Why Not Using Globals is Good

It's one thing to say that global variables are evil. It's another to say that not using globals is explicitly good.

My favorite example is a 2017 GDC talk titled Replay Technology in 'Overwatch': Kill Cam, Gameplay, and Highlights.

Overwatch is a multiplayer first person shooter. A highly desired feature in these games is a "kill cam". When you die you get a replay of the last few seconds from the killer's perspective. When you get shotgunned in the back you can see how it happened.

This is a remarkably complex feature. It requires some form of rolling back the world and replaying it from a different perspective.

Overwatch Killcam Timeline

Things get more complicated when the replay can be snap-interrupted. Players may cancel the replay out of frustration. Even more complex, they might get resurrected by an ally's ability. Resurrection is particularly nasty because it means the replay world state can't ignore "live" gameplay events.

Blizzard's solution to this is genius. They create two fully disjoint "domains". A "replay domain" is populated upon death and rendered to the client viewport. While this is happening the "live domain" is still processing updates. The renderer can snap between the two at any time.

Supporting this required the elimination of all global variables. The domains must be entirely independent.

This was so successful they wound up with four domains. Live, Replay, Lobby, and an orchestrator.

I think this solution is so cool. It's simple and elegant. And it doesn't work if you have global variables or singletons. The presentation is ~50 minutes long and I highly recommend watching the whole thing.

Exceptions

Are global variables always evil and unsafe? Are there any cases where they're acceptable? Probably, but they're exceedingly rare IMHO.

Trace logging is probably worth a global. It's not reasonable to pass a logging param to every function. Normal logging I'm more on the fence about. It's maybe worth a global. However logging globals have also caused me mountains of pain. So I'm not convinced.

Are there other cases? Honestly I'm not sure. Maybe! I'm sure folks will let me know in the comments. They exist, but they're few and far between.

Closing Thoughts

It is my opinion that global variables are evil and unsafe and should be avoided. Using globals introduces bugs, complexity, and unwarranted constraints. Avoiding globals is less error prone, simpler, and more flexible.

Even single-threaded systems should avoid globals. This way you can support two independent systems on different threads. This is especially important on Linux where shared libraries also prefer to share globals.

The sad part is globals don't even add much value. They let you be lazy. Globals provide a teeny tiny benefit, but come with a large and long-term price.

I will not go so far as to say global variables shouldn't be supported by programming languages. I thought about it. It's close. Ultimately I do think it's worth an escape hatch. However that escape hatch should be bracketed with unsafe_and_evil to discourage use and force programmers to make a conscious decision.

Maybe Carbon can be convinced to adopt unsafe_and_evil. :)

Thanks for reading.

What is a Global Variable

For the sake of clarity, what is a global variable? Here's an example in C++. However the concepts apply to any language.

// foo.cpp
static int evil_1; 
int evil_2; 

void DoStuff() {
    static int evil_3;
    thread_local int evil_4; 
}

// foo.h
extern int evil_5; 
int evil_6;

struct Foo {
    static int evil_7; 
};

Everyone one of those variable declarations is evil. I didn't cover every permutation. They're all bad.

Global constants are NOT evil and you are free to use them when appropriate.

// All constant, all fine
const int fine_1; 
constexpr int fine_2; 
constexpr const int fine_3; 

Another phrase would be "mutable global state is evil". Is a "constant" a "variable"? Meh. Different languages use different terms.