← Previous
2026-07-23 | Programming | C | Language Design | Rants

Not a Big Fan of Defer

Apologies in advance for what may be bikeshedding, but: why is a defer keyword such a popular 1 2 3 choice in modern programming languages?

The simplest answer is, “because it’s convenient”. Initializing something usually implies a matching cleanup procedure that also has to run. For the code to be correct, every init has to have a matching cleanup (for example, fopen and fclose). Writing defer puts these next to one another, which is ergonomic:

int MyFunction(void) {
    File *f = fopen("mydata.bin", "rb");
    defer if (f) fclose(f);

    // Do some stuff with f...
    // ...even if that stuff has an early return, fclose is guaranteed
    // do be called.
}

That’s pretty nice. Otherwise, you end up having to manually check that every return is preceded by an fclose(..). If the function uses many files, or lots of things that need to be dealt with (maybe it allocates memory that it has to free), then things get ugly fast.

But this also gets at the essential reason I don’t like defer: it makes program flow needlessly non-linear. This is a pain when debugging, especially if you do not have a debugger and are trying to determine the program’s flow by sprinkling it with print statements.

Seriously, why is the primary debugger on Linux (GDB) an unusable mess? It's 2026!

Even if you’re using a debugger, which defer statements will run depends on which blocks of code previously ran. This is bad news if there are data dependencies between the deferred statements. It may be fairly hard to determine, just by reading the code, whether or not all the right destructors are guaranteed to run in the correct order. Also, most people learn about a program’s flow by reading it, even if they do have a debugger on hand; this is one of the many reasons we try to keep the number of branching paths to a minimum, and we we try to avoid deep delegation chains of functions calling functions calling functions. The defer statement causes the same sort of “mental cache miss” by forcing the reader to jump around in the code a lot.

One alternative to defer is to disallow the use of early return. If you follow Single-Entry Single-Exit rules in your code, the only place return is allowed is at the bottom of the function. In this case, you can put all the cleanup code right above that return statement, since you are guaranteed to get there.

Proponents of defer might argue that this separates the initialization and cleanup by possibly hundreds of lines, depending how long the function is. That’s true, but I don’t think it matters that much in practice. Whether you use defer or not, the only way to verify that a function is cleanup-correct – other than having written it recently – is to go through it line by line looking for statements which allocate or initialize something (you can’t just grep for defer because the failure mode you’re searching for is an allocation missing a defer.)

If you follow SESE, you can also improve your life by making sure your cleanup code goes in the opposite order as the initializations: first to initialize should be last to clean up (defer implementations usually obey this schedule automatically).

Usually SESE code ends up using an “early-exit” variable; when it is set, most of the important blocks get skipped. For example, suppose we needed to write a bunch of data to a file:

FILE *f = open("/path/to/file", "w");
bool ok = !!f;
if (ok) {
    int result = fprintf(f, "data part 1\n");
    if (result < 0) ok = false;
}
if (ok) {
    int result = fprintf(f, "data part 2\n");
    if (result < 0) ok = false;
}
if (f) fclose(f);

In C and other languages that have it, the humble goto tidies things up here if you don’t like extra typing. This is the one place I allow goto in my own code. It adds a footgun, namely that variables can be in scope without being initialized, but you can avoid this in lots of ways (the best is to declare all resource variables at the top of the function, as you would do in strict C89).

int succeeded = false;
FILE *f = open("/path/to/file", "w");
if (!f) goto finish;

if (fprintf(f, "data part 1\n") < 0) goto finish;
if (fprintf(f, "data part 2\n") < 0) goto finish;

succeeded = true;
finish:
if (f) fclose(f);
// 'succeeded' now has the same meaning as 'ok' in the previous example

Of course, using this means you can’t use linters that prohibit goto. If your programmers are already producing a lot of entropy, allowing goto may have even more disastrous effects.

For those not aware, Goto Considered Harmful is essential reading for all programmers.

If you decide that bringing back goto is a bit much – that’s reasonable. It’s a feature in C, which I use, and defer is not, so that makes the choice simple for me. But for the reasons above: if I switched to a language that had defer but no goto, I’d probably avoid the defer where possible and stick to something like SESE.