[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)