[HN Gopher] The Defer Technical Specification: It Is Time
___________________________________________________________________
The Defer Technical Specification: It Is Time
Author : mattjhall
Score : 40 points
Date : 2025-03-16 14:20 UTC (3 days ago)
(HTM) web link (thephd.dev)
(TXT) w3m dump (thephd.dev)
| Mond_ wrote:
| 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.
| codr7 wrote:
| 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.
|
| https://github.com/codr7/hacktical-c/tree/main/macro
| wahern wrote:
| 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.
| Animats wrote:
| 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 wrote:
| 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 wrote:
| > 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.
| Jtsummers wrote:
| > 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.
| topspin wrote:
| 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.
| dgunay wrote:
| It's incredibly ugly but you could sort of hack in a smaller-
| scoped defer using anonymous functions:
| https://go.dev/play/p/VgnprcObPHz
| hinkley wrote:
| 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... "
| throw-qqqqq wrote:
| 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 wrote:
| Does go's defer allocate on the heap? I thought it would only
| do that if necessary.
| pkaye wrote:
| 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-.
| ..
| zyedidia wrote:
| 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.
| lelanthran wrote:
| 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.
| ayende wrote:
| 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
| loeg wrote:
| 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;
| bobmcnamara wrote:
| 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;
| kats wrote:
| 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 wrote:
| 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_ wrote:
| 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 wrote:
| 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.
| lukaslalinsky wrote:
| 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.
___________________________________________________________________
(page generated 2025-03-19 23:00 UTC)