The Defer Technical Specification: It Is Time
30 comments
·March 16, 2025Jtsummers
> The central idea behind defer is that, unlike its Go counterpart, defer in C is lexically bound, or “translation-time” only, or “statically scoped”. What that means is that defer runs unconditionally at the end of the block or the scope it is bound to based on its lexical position in the order of the program.
The only reasonable way for defer to behave. Function scoped never made sense to me given the wasted potential. The demonstration with loop and mutex being a good one.
chrsig
yeah, I've always just extracted the loop body into a new function as a result
topspin
Regarding the statements on golang's defer:
"the defer call is hoisted to the outside of the for loop in func work"
Astonishing. Add that to the list of golang head scratchers. That is one of the biggest "principle of least astonishment" violations I've ever seen.Disclaimer: Not a golang hater. Great language. Used it myself on occasion, although I remain a golang neophyte. Put away the sharp objects.
hinkley
I love the Principle of Least Astonishment, but I first encountered it in the Ruby book and I gave up reading it halfway through because I kept thinking, "He and I have very different definitions of astonishing..."
dgunay
It's incredibly ugly but you could sort of hack in a smaller-scoped defer using anonymous functions: https://go.dev/play/p/VgnprcObPHz
throw-qqqqq
Another cool difference between this and Go’s ‘defer’, is that it doesn’t allocate memory on the heap. Go’s ‘defer’ does and it has a small performance cost compared to just calling the .release() or whatever yourself… shrugs
At least this was the case last I did benchmarks of my Go code. Dno if they changed that.
klodolph
Does go’s defer allocate on the heap? I thought it would only do that if necessary.
pkaye
I know they implemented an optimization back in go 1.13. Not sure if that will help.
https://github.com/golang/proposal/blob/master/design/34481-...
Animats
Ugh.
Go's "defer" is reasonably clean because Go is garbage-collected. So you don't have to worry about something being deleted before a queued "defer" runs. That's well-behaved. This is going to be full of ugly, non-obvious problems.
Interestingly, it's not really "defer" in the Go sense. It's "finally", in the try/finally sense of C++, using Go-type "defer" syntax". This mostly matters for scope and ownership issues. If you want to close a file in a defer, and the file is a local variable, you have to be sure that the close precedes the end of block de-allocation. Most of the discussion in the article revolves around how to make such problems behave halfway decently.
"defer" happens invisibly, in the background. That's contrary to the basic simplicity of C, where almost nothing happens invisibly.
codr7
The point of defer is to put the cleanup logic in one place for local variables though, so the risk of someone else deleting it isn't a thing.
jayd16
> It's "finally", in the try/finally sense of C++
What sense is that? C++ doesn't have finally and the article explicitly calls out how its not like destructors.
null
Mond_
Seems like a perfect fit for C, and glad to see we're trying to avoid stepping into that funny pitfall Go has with its function-scoped defer keyword.
Glad to see C is evolving and standardizing.
zyedidia
What is the recommended way to use defer to free values only on an error path (rather than all paths)? Currently I use goto for this:
void* p1 = malloc(...);
if (!p1) goto err1;
void* p2 = malloc(...);
if (!p2) goto err2;
void* p3 = malloc(...);
if (!p3) goto err3;
return {p1, p2, p3};
err3: free(p2);
err2: free(p1);
err1: return NULL;
With defer I think I would have to use a "success" boolean like this: bool success = false;
void* p1 = malloc(...);
if (!p1) return NULL;
defer { if (!success) free(p1) }
void* p2 = malloc(...);
if (!p2) return NULL;
defer { if (!success) free(p2) }
void* p3 = malloc(...);
if (!p3) return NULL;
defer { if (!success) free(p3) }
success = true;
return {p1, p2, p3};
I'm not sure if this has really improved things. I do see the use-case for locks and functions that allocate/free together though.loeg
Can also use a different variable name for the success case and null out any successfully consumed temporaries.
void* p1 = malloc();
if (!p1) return failure;
defer { free(p1); }
...
someOther->pointer = p1;
p1 = NULL;
return success;
lelanthran
I don't even bother with `error1`, `error2`, ... `errorN`.
I initialise all pointers to NULL at the top of the function and use `goto cleanup`, which cleans up everything that is not being returned ... because `free(some_ptr)` where `some_ptr` is NULL is perfectly legal.
bobmcnamara
I'm not sure I'd do either for this trivial case, but it might make sense where the cleanup logic is more complex?
void* p1 = malloc(...);
void* p2 = malloc(...);
void* p3 = malloc(...);
if(p1 && p2 && p3)
return {p1, p2, p3};
free(p3);
free(p2);
free(p1);
return NULL;
ayende
That is a well structure system, yes Both cleanup for error and allocation happens in the same place
That means you won't forget to call it, and the success flag is an obvious way to ha dle it
null
lukaslalinsky
Ever since I started working with Zig, I came to realization that its errdefer is even more useful than defer itself. But you can't implement errdefer in C, since there is no standard/disambiguous way of returning errors.
codr7
I've been doing properly scoped defers in C since forever, as long as you have access to cleanup attributes and nested functions it's no big deal.
wahern
Yes, the proposal is tailored so that other than simple syntax support no new semantics need to be implemented within GCC to support defer, though clang will need to finally add support for nested functions--in spirit if not the literal GCC extension.[1] The proposal also gives consideration to MSVC's try/finally to minimize the amount of effort required there to support defer.
[1] Because defer takes a block, not a simple statement. And deferred blocks can be defined recursively--i.e. defer within a defer block.
null
kats
Dude somebody stop this guy. Just go use another language. C is a small language. Once you're done playing 'Make my own C++' there will be all this toxic complexity in the C standard which nobody asked for, and C programmers will be stuck with it for decades.
kats
There are already many mature languages that do what the author wants. The differences are just minor gripes/bikeshedding etc.
It's for emotional reasons why the author wants to edit C, the same reason they call themselves 'The PhD'.
_kst_
The author is the project editor for the ISO C standard.
(And I hardly think that analyzing speculating about the motivation for the author's chosen nickname is constructive.)
kats
Great! I'm a programmer. And I've sure spent too much time on C++isms.
> (And I hardly think that analyzing speculating about the motivation for the author's chosen nickname is constructive.)
Nope! Gets right to it. This is really building C++ (but this time how I want). It adds work for every C programmer who has to check off a whole bunch of small tasks to keep a codebase living for many years.
The author takes great care to rebut a common theme among objections to the proposal - “this isn’t necessary if you just write code better”. I am reminded of this fantastic essay:
> If we flew planes like we write code, we’d have daily crashes, of course, but beyond that, the response to every plane crash would be: “only a bad pilot blames their plane!”
> This doesn’t happen in aviation, because in aviation we have decided, correctly, that human error is an intrinsic and inseparable part of human activity. And so we have built concentric layers of mechanical checks and balances around pilots, to take on part of the load of flying. Because humans are tired, they are burned out, they have limited focus, limited working memory, they are traumatized by writing executable YAML, etc.
> Mechanical processes are independent of the skill of the programmer. Mechanical processes scale, unlike berating people to simply write fewer bugs.
(https://borretti.me/article/introducing-austral#goals)