Defer available in gcc and clang

2026-02-165:27263251gustedt.wordpress.com

About a year ago I posted about defer and that it would be available for everyone using gcc and/or clang soon. So it is probably time for an update. Two things have happened in the mean time: A tec…

About a year ago I posted about defer and that it would be available for everyone using gcc and/or clang soon. So it is probably time for an update.

Two things have happened in the mean time:

  • A technical specification (TS 25755) edited by JeanHeyd Meneide is now complete and moves through ISO’s complicated publication procedures.
  • Both gcc and clang communities have worked on integrating this feature into their C implementations.

I have not yet got my hands on the gcc implementation (but this is also less urgent, see below), but I have been able to use clang’s which is available starting with clang-22.

I think that with this in mind everybody developing in C could and should now seriously consider switching to defer for their cleanup handling:

  • no more resource leakage or blocked mutexes on rarely used code paths,
  • no more spaghetti code just to cover all possibilities for preliminary exits from functions.

I am not sure if the compiler people are also planning back ports of these features, but with some simple work around and slightly reduced grammar for the defer feature this works for me from gcc-9 onward and for clang-22 onward:

#if __has_include(<stddefer.h>)
# include <stddefer.h>
# if defined(__clang__)
#  if __is_identifier(_Defer)
#   error "clang may need the option -fdefer-ts for the _Defer feature"
#  endif
# endif
#elif __GNUC__ > 8
# define defer _Defer
# define _Defer      _Defer_A(__COUNTER__)
# define _Defer_A(N) _Defer_B(N)
# define _Defer_B(N) _Defer_C(_Defer_func_ ## N, _Defer_var_ ## N)
# define _Defer_C(F, V)                                                 \
  auto void F(int*);                                                    \
  __attribute__((__cleanup__(F), __deprecated__, __unused__))           \
     int V;                                                             \
  __attribute__((__always_inline__, __deprecated__, __unused__))        \
    inline auto void F(__attribute__((__unused__)) int*V)
#else
# error "The _Defer feature seems not available"
#endif

So this is already a large panel of compilers. Obviously it depends on your admissible compile platforms whether or not these are sufficient for you. In any case, with these you may compile for a very wide set of installs since defer does not need any specific software infrastructure or library once the code is compiled.

As already discussed many times, the gcc fallback uses the so-called “nested function” feature which is always subject of intense debate and even flame wars. Don’t worry, the implementation as presented here, even when compiled with no optimization at all, does not produce any hidden function in the executable, and in particular there is no “trampoline” or whatever that would put your execution at risk of a stack exploit.

You may also notice that there is no fallback for older clang version. This is because their so-called “blocks” extension cannot easily be used as a drop-in to replace nested function: their semantics to access variables from the surrounding scope are different and not compatible with the defer feature as defined by TS 25755.

So for example if you are scared of using big VLA on the stack, you may use the above code in headers and something like

double* BigArray
  = malloc(sizeof(double[aLot]));
if (!BigArray {
  exit(EXIT_FALURE);
}
defer { 
  free(BigArray); 
}

to have an implementation of a big array with a failure mode for the allocation.

Or if you want to be sure that all your mutexes are unlocked when you leave a critical section, use and idiom as here

{
  if (mtx_lock(&mtx) != thrd_success) {
    exit(EXIT_FAILURE);
  }
  defer {
    mtx_unlock(&mtx);
  }

  ... do something complicated ...

  if (rareCondition) {
    return 42;
  }

  ... do something even more complicated ...
}

Just notice, that you’d always have to use the defer feature with curly braces to ensure that the gcc fallback works smoothly.


Read the original article

Comments

  • By kjgkjhfkjf 2026-02-205:335 reply

    The article is a bit dense, but what it's announcing is effectively golang's `defer` (with extra braces) or a limited form of C++'s RAII (with much less boilerplate).

    Both RAII and `defer` have proven to be highly useful in real-world code. This seems like a good addition to the C language that I hope makes it into the standard.

    • By Zambyte 2026-02-205:504 reply

      Probably closer to defer in Zig than in Go, I would imagine. Defer in Go executes when the function deferred within returns; defer in Zig executes when the scope deferred within exits.

      • By rwmj 2026-02-208:16

        This is the crucial difference. Scope-based is much better.

        By the way, GCC and Clang have attribute((cleanup)) (which is the same, scope-based clean-up) and have done for over a decade, and this is widely used in open source projects now.

      • By CodesInChaos 2026-02-2010:022 reply

        I wonder what the thought process of the Go designers was when coming up with that approach. Function scope is rarely what a user needs, has major pitfalls, and is more complex to implement in the compiler (need to append to an unbounded list).

        • By 0xjnml 2026-02-2012:171 reply

          > I wonder what the thought process of the Go designers was when coming up with that approach.

          Sometimes we need block scoped cleanup, other times we need the function one.

          You can turn the function scoped defer into a block scoped defer in a function literal.

          AFAICT, you cannot turn a block scoped defer into the function one.

          So I think the choice was obvious - go with the more general(izable) variant. Picking the alternative, which can do only half of the job, would be IMO a mistake.

          • By aw1621107 2026-02-2016:10

            > AFAICT, you cannot turn a block scoped defer into the function one.

            You kinda-sorta can by creating an array/vector/slice/etc. of thunks (?) in the outer scope and then `defer`ing iterating through/invoking those.

        • By mort96 2026-02-2010:111 reply

          I hate that you can't call defer in a loop.

          I hate even more that you can call defer in a loop, and it will appear to work, as long as the loop has relatively few iterations, and is just silently massively wasteful.

          • By usrnm 2026-02-2010:181 reply

            The go way of dealing with it is wrapping the block with your defers in a lambda. Looks weird at first, but you can get used to it.

            • By mort96 2026-02-2010:212 reply

              I know. Or in some cases, you can put the loop body in a dedicated function. There are workarounds. It's just bad that the wrong way a) is the most obvious way, and b) is silently wrong in such a way that it appears to work during testing, often becoming a problem only when confronted with real-world data, and often surfacing only as being a hard-to-debug performance or resource usage issue.

              • By 9rx 2026-02-2013:482 reply

                What's the use-case for block-level defer?

                In a tight loop you'd want your cleanup to happen after the fact. And in, say, an IO loop, you're going to want concurrency anyway, which necessarily introduces new function scope.

                • By mort96 2026-02-2014:121 reply

                  > In a tight loop you'd want your cleanup to happen after the fact.

                  Why? Doing 10 000 iterations where each iteration allocates and operates a resource, then later going through and freeing those 10 000 resources, is not better than doing 10 000 iterations where each iteration allocates a resource, operates on it, and frees it. You just waste more resources.

                  > And in, say, an IO loop, you're going to want concurrency anyway

                  This is not necessarily true; not everything is so performance sensitive that you want to add the significant complexity of doing it async. Often, a simple loop where each iteration opens a file, reads stuff from it, and closes it, is more than good enough.

                  Say you have a folder with a bunch of data files you need to work on. Maybe the work you do per file is significant and easily parallelizable; you would probably want to iterate through the files one by one and process each file with all your cores. There are even situations where the output of working on one file becomes part of the input for work on the next file.

                  Anyway, I will concede that all of this is sort of an edge case which doesn't come up that often. But why should the obvious way be the wrong way? Block-scoped defer is the most obvious solution since variable lifetimes are naturally block-scoped; what's the argument for why it ought to be different?

                • By nasretdinov 2026-02-2014:132 reply

                  Let's say you're opening files upon each loop iteration. If you're not careful you'll run out of open file descriptors before the loop finishes.

                  • By mort96 2026-02-2014:242 reply

                    It doesn't just have to be files, FWIW. I once worked in a Go project which used SDL through CGO for drawing. "Widgets" were basically functions which would allocate an SDL surface, draw to it using Cairo, and return it to Go code. That SDL surface would be wrapped in a Go wrapper with a Destroy method which would call SDL_DestroySurface.

                    And to draw a surface to the screen, you need to create an SDL texture from it. If that's all you want to do, you can then destroy the SDL surface.

                    So you could imagine code like this:

                        strings := []string{"Lorem", "ipsum", "dolor", "sit", "amet"}
                        
                        stringTextures := []SDLTexture{}
                        for _, s := range strings {
                            surface := RenderTextToSurface(s)
                            defer surface.Destroy()
                            stringTextures = append(stringTextures, surface.CreateTexture())
                        }
                    
                    Oops, you're now using way more memory than you need!

                    • By win311fwg 2026-02-2014:341 reply

                      Why would you allocate/destroy memory in each iteration when you can reuse it to much greater effect? Aside from bad API design, but a language isn't there to paper over bad design decisions. A good language makes bad design decisions painful.

                      • By mort96 2026-02-2014:391 reply

                        The surfaces are all of different size, so the code would have to be more complex, resizing some underlying buffer on demand. You'd have to split up the text rendering into an API to measure the text and an API to render the text, so that you could resize the buffer. So you'd introduce quite a lot of extra complexity.

                        And what would be the benefit? You save up to one malloc and free per string you want to render, but text rendering is so demanding it completely drowns out the cost of one allocation.

                        • By win311fwg 2026-02-2014:571 reply

                          Why does the buffer need to be resized? Your malloc version allocates a fixed amount of memory on each iteration. You can allocate the same amount of memory ahead of time.

                          If you were dynamically changing the malloc allocation size on each iteration then you have a case for a growable buffer to do the same, but in that case you would already have all the complexity of which you speak as required to support a dynamically-sized malloc.

                          • By mort96 2026-02-2015:212 reply

                            The example allocates an SDL_Surface large enough to fit the text string each iteration.

                            Granted, you could do a pre-pass to find the largest string and allocate enough memory for that once, then use that buffer throughout the loop.

                            But again, what do you gain from that complexity?

                            • By win311fwg 2026-02-2015:271 reply

                              > The example allocates an SDL_Surface large enough to fit the text string each iteration.

                              Impossible without knowing how much to allocate, which you indicate would require adding a bunch of complexity. However, I am willing to chalk that up to being a typo. Given that we are now calculating how much to allocate on each iteration, where is the meaningful complexity? I see almost no difference between:

                                  while (next()) {
                                      size_t size = measure_text(t);
                                      void *p = malloc(size);
                                      draw_text(p, t);
                                      free(p);
                                  }
                              
                              and

                                  void *p = NULL;
                                  while (next()) {
                                      size_t size = measure_text(t);
                                      void *p = galloc(p, size);
                                      draw_text(p, t);
                                  }
                                  free(p);

                              • By mort96 2026-02-2015:441 reply

                                >> The example allocates an SDL_Surface large enough to fit the text string each iteration.

                                > Impossible without knowing how much to allocate

                                But we do know how much to allocate? The implementation of this example's RenderTextToSurface function would use SDL functions to measure the text, then allocate an SDL_Surface large enough, then draw to that surface.

                                > I see almost no difference between: (code example) and (code example)

                                What? Those two code examples aren't even in the same language as the code I showed.

                                The difference would be between the example I gave earlier:

                                    stringTextures := []SDLTexture{}
                                    for _, str := range strings {
                                        surface := RenderTextToSurface(str)
                                        defer surface.Destroy()
                                        stringTextures = append(stringTextures, surface.CreateTexture())
                                    }
                                
                                and:

                                    surface := NewSDLSurface(0, 0)
                                    defer surface.Destroy()
                                    stringTextures := []SDLTexture{}
                                    for _, str := range strings {
                                        size := MeasureText(s)
                                        if size.X > surface.X || size.Y > surface.Y {
                                            surface.Destroy()
                                            surface = NewSDLSurface(size.X, size.Y)
                                        }
                                
                                        surface.Clear()
                                        RenderTextToSurface(surface, str)
                                        stringTextures = append(stringTextures, surface.CreateTextureFromRegion(0, 0, size.X, size.Y))
                                    }
                                
                                Remember, I'm talking about the API to a Go wrapper around SDL. How the C code would've looked if you wrote it in C is pretty much irrelevant.

                                I have to ask again though, since you ignored me the first time: what do you gain? Text rendering is really really slow compared to memory allocation.

                                • By win311fwg 2026-02-2015:511 reply

                                  > Remember, I'm talking about the API to a Go wrapper around SDL.

                                  We were talking about using malloc/free vs. a resizable buffer. Happy to progress the discussion towards a Go API, however. That, obviously, is going to look something more like this:

                                      renderer := SDLRenderer()
                                      defer renderer.Destroy()
                                      for _, str := range strings {
                                          surface := renderer.RenderTextToSurface(str)
                                          textures = append(textures, renderer.CreateTextureFromSurface(surface))
                                      }
                                  
                                  I have no idea why you think it would look like that monstrosity you came up with.

                                  • By mort96 2026-02-2015:561 reply

                                    > No. We were talking about using malloc/free vs. a resizable buffer.

                                    No. This is a conversation about Go. My example[1], that you responded to, was an example taken from a real-world project I've worked on which uses Go wrappers around SDL functions to render text. Nowhere did I mention malloc or free, you brought those up.

                                    The code you gave this time is literally my first example (again, [1]), which allocates a new surface every time, except that you forgot to destroy the surface. Good job.

                                    Can this conversation be over now?

                                    [1] https://news.ycombinator.com/item?id=47088409

                                    • By win311fwg 2026-02-2015:581 reply

                                      I invite you to read the code again. You missed a few things. Notably it uses a shared memory buffer, as discussed, and does free it upon defer being executed. It is essentially equivalent to the second C snippet above, while your original example is essentially equivalent to the first C snippet.

                                      • By mort96 2026-02-2016:061 reply

                                        Wait, so your wrapper around SDL_Renderer now also inexplicably contains a scratch buffer? I guess that explains why you put RenderTextToSurface on your SDL_Renderer wrapper, but ... that's some really weird API design. Why does the SDL_Renderer wrapper know how to use SDL_TTF or PangoCairo to draw text to a surface? Why does SDL_Renderer then own the resulting surface?

                                        To anyone used to SDL, your proposed API is extremely surprising.

                                        It would've made your point clearer if you'd explained this coupling between SDL_Renderer and text rendering in your original post.

                                        But yes, I concede that if there was any reason to do so, putting a scratch surface into your SDL_Renderer that you can auto-resize and render text to would be a solution that makes for slightly nicer API design. Your SDL_Renderer now needs to be passed around as a parameter to stuff which only ought to need to concern itself with CPU rendering, and you now need to deal with mutexes if you have multiple goroutines rendering text, but those would've been alright trade-offs -- again, if there was a reason to do so. But there's not; the allocation is fast and the text rendering is slow.

                                        • By win311fwg 2026-02-2016:121 reply

                                          You're right to call out that the SDLRenderer name was a poor choice. SDL is an implementation detail that should be completely hidden from the user of the API. That it may or may not use SDL under the hood is irrelevant to the user of the API. If the user wanted to use SDL, they would do so directly. The whole point of this kind of abstraction, of course, is to decouple of the dependence on something like SDL. Point taken.

                                          Aside from my failure in dealing with the hardest problem in computer science, how would you improve the intent of the API? It is clearly improved over the original version, but we would do well to iterate towards something even better.

                                          • By mort96 2026-02-2016:141 reply

                                            I think the most obvious improvement would be: just make it a free function which returns a surface, text rendering is slow and allocation is fast

                                            • By win311fwg 2026-02-2016:171 reply

                                              That is a good point. If text rendering is slow, why are you not doing it in parallel? This is what 9rx called out earlier.

                                              • By mort96 2026-02-2016:241 reply

                                                Some hypothetical example numbers: if software-rendering text takes 0.1 milliseconds, and I have a handful of text strings to render, I may not care that rendering the strings takes a millisecond or two.

                                                But that 0.1 millisecond to render a string is an eternity compared to the time it takes to allocate some memory, which might be on the order of single digit microseconds. Saving a microsecond from a process which takes 0.1 milliseconds isn't noticeable.

                                                • By win311fwg 2026-02-2016:31

                                                  You might not care today, but the next guy tasked to render many millions of strings tomorrow does care. If he has to build yet another API that ultimately does the same thing and is almost exactly the same, something has gone wrong. A good API is accommodating to users of all kinds.

                            • By krapp 2026-02-2015:361 reply

                              I think I've been successfully nerd sniped.

                              It might be preferable to create a font atlas and just allocate printable ASCII characters as a spritesheet (a single SDL_Texture* reference and an array of rects.) Rather than allocating a texture for each string, you just iterate the string and blit the characters, no new allocations necessary.

                              If you need something more complex, with kerning and the like, the current version of SDL_TTF can create font atlases for various backends.

                              • By mort96 2026-02-2015:52

                                Completely depends on context. If you're rendering dynamically changing text, you should do as you say. If you have some completely static text, there's really nothing wrong with doing the text rendering once using PangoCairo and then re-using that texture. Doing it with PangoCairo also lets you do other fancy things like drop shadows easier.

                  • By 9rx 2026-02-2014:252 reply

                    Files are IO, which means a lot of waiting. For what reason wouldn't you want to open them concurrently?

                    • By mort96 2026-02-2014:29

                      Opening a file is fairly fast (at least if you're on Linux; Windows not so much). Synchronous code is simpler than concurrent code. If processing files sequentially is fast enough, for what reason would you want to open them concurrently?

                    • By nasretdinov 2026-02-2014:491 reply

                      For concurrent processing you'd probably do something like splitting the file names into several batches and process those batches sequentially in each goroutine, so it's very much possible that you'd have an exact same loop for the concurrent scenario.

                      P.S. If you have enough files you don't want to try to open them all at once — Go will start creating more and more threads to handle the "blocked" syscalls (open(2) in this case), and you can run out of 10,000 threads too

                      • By win311fwg 2026-02-2015:093 reply

                        You'd probably have to be doing something pretty unusual to not use a worker queue. Your "P.S." point being a perfect case in point as to why.

                        If you have a legitimate reason for doing something unusual, it is fine to have to use the tools unusually. It serves as a useful reminder that you are purposefully doing something unusual rather than simply making a bad design choice. A good language makes bad design decisions painful.

                        • By mort96 2026-02-2015:301 reply

                          You have now transformed the easy problem of "iterate through some files" into the much more complex problem of either finding a work queue library or writing your own work queue library; and you're baking in the assumption that the only reasonable way to use that work queue is to make each work item exactly one file.

                          What you propose is not a bad solution, but don't come here and pretend it's the only reasonable solution for almost all situations. It's not. Sometimes, you want each work item to be a list of files, if processing one file is fast enough for synchronisation overhead to be significant. Often, you don't have to care so much about the wall clock time your loop takes and it's fast enough to just do sequentially. Sometimes, you're implementing a non-important background task where you intentionally want to only bother one core. None of these are super unusual situations.

                          It is telling that you keep insisting that any solution that's not a one-file-per-work-item work queue is super strange and should be punished by the language's design, when you haven't even responded to my core argument that: sometimes sequential is fast enough.

                          • By win311fwg 2026-02-2015:481 reply

                            > It is telling that you keep insisting

                            Keep insisting? What do you mean by that?

                            > when you haven't even responded to my core argument that: sometimes sequential is fast enough.

                            That stands to reason. I wasn't responding to you. The above comment was in reply to nasretdinov.

                            • By mort96 2026-02-2015:59

                              Your comment was in reply to nasretdinov, but its fundamental logic ignores what I've been telling you this whole time. You're pretending that the only solution to iterating through files is a work queue and that any solution that does a synchronous open/close for each iteration is fundamentally bad. I have told you why it isn't: you don't always need the performance.

                        • By nasretdinov 2026-02-2016:331 reply

                          Using a "work queue", i.e. a channel would still have a for loop like

                            for filename := range workQueue {
                                fp, err := os.Open(filename)
                                if err != nil { ... }
                                defer fp.Close()
                                // do work
                            }
                          
                          
                          Which would have the same exact problem :)

                          • By win311fwg 2026-02-2016:401 reply

                            I don't see the problem.

                                for _, filename := range files {
                                    queue <- func() {
                                        f, _ := os.Open(filename)
                                        defer f.Close()
                                    }
                                }
                            
                            or more realistically,

                                var group errgroup.Group
                                group.SetLimit(10)
                                for _, filename := range files {
                                    group.Go(func() error {
                                        f, err := os.Open(filename)
                                        if err != nil {
                                            return fmt.Errorf("failed to open file %s: %w", filename, err)
                                        }
                                        defer f.Close()  
                                        // ...
                                        return nil          
                                    })
                                }
                                if err := group.Wait(); err != nil {
                                    return fmt.Errorf("failed to process files: %w", err)
                                }
                            
                            Perhaps you can elaborate?

                            I did read your code, but it is not clear where the worker queue is. It looks like it ranges over (presumably) a channel of filenames, which is not meaningfully different than ranging over a slice of filenames. That is the original, non-concurrent solution, more or less.

                            • By mort96 2026-02-2017:241 reply

                              I think they imagine a solution like this:

                                  // Spawn workers
                                  for _ := range 10 {
                                      go func() {
                                          for path := range workQueue {
                                              fp, err := os.Open(path)
                                              if err != nil { ... }
                                              defer fp.Close()
                                              // do work
                                          }
                                      }()
                                  }
                              
                                  // Iterate files and give work to workers
                                  for _, path := range paths {
                                      workQueue <- path
                                  }

                              • By win311fwg 2026-02-2018:491 reply

                                Maybe, but why would one introduce coupling between the worker queue and the work being done? That is a poor design.

                                Now we know why it was painful. What is interesting here is that the pain wasn't noticed as a signal that the design was off. I wonder why?

                                We should dive into that topic. I suspect at the heart of it lies why there is so much general dislike for Go as a language, with it being far less forgiving to poor choices than a lot of other popular languages.

                                • By mort96 2026-02-2019:061 reply

                                  I think your issue is that you're an architecture astronaut. This is not a compliment. It's okay for things to just do the thing they're meant to do and not be super duper generic and extensible.

                                  • By win311fwg 2026-02-2019:25

                                    It is perfectly okay inside of a package. Once you introduce exports, like as seen in another thread, then there is good reason to think more carefully about how users are going to use it. Pulling the rug out from underneath them later when you discover your original API was ill-conceived is not good citizenry.

                                    But one does still have to be mindful if they want to write software productively. Using a "super duper generic and extensible" solution means that things like error propagation is already solved for you. Your code, on the other hand, is going to quickly become a mess once you start adding all that extra machinery. It didn't go unnoticed that you conveniently left that out.

                                    Maybe that no longer matters with LLMs, when you don't even have to look the code and producing it is effectively free, but LLMs these days also understand how defer works so then this whole thing becomes moot.

      • By bashkiddie 2026-02-209:351 reply

        I would like to second this.

        In Golang if you iterate over a thousand files and

            defer File.close()
        
        your OS will run out of file descriptors

      • By jibal 2026-02-2014:30

        defer was invented by Andrei Alexandrescu who spelled it scope(exit)/scope(failure) [Zig's errdefer]/scope(success) ... it first appeared in D 2.0 after Andrei convinced Walter Bright to add it.

    • By L-4 2026-02-207:572 reply

      Both defer and RAII have proven to be useful, but RAII has also proven to be quite harmful in cases, in the limit introducing a lot of hidden control flow.

      I think that defer is actually limited in ways that are good - I don't see it introducing surprising control flow in the same way.

      • By kibwen 2026-02-2013:281 reply

        Defer is also hidden control flow. At the end of every block, you need to read backwards in the entire block to see if a defer was declared in order to determine where control will jump to. Please stop pretending that defer isn't hidden control flow.

        > RAII has also proven to be quite harmful in cases

        The downsides of defer are much worse than the "downsides" of RAII. Defer is manual and error-prone, something that you have to remember to do every single time.

        • By sparkie 2026-02-2014:321 reply

          Defer is a restricted form of COMEFROM with automatic labels. You COMEFROM the end of the next `defer` block in the same scope, or from the end of the function (before `return`) if there is no more `defer`. The order of execution of defer-blocks is backwards (bottom-to-top) rather than the typical top-to-bottom.

              puts("foo");
              defer { puts("bar"); }
              puts("baz");
              defer { puts("qux"); }
              puts("corge");
              return;
          
          Will evaluate:

              puts("foo");
              puts("baz");
              puts("corge");
              puts("qux");
              puts("bar");
              return;

          • By vlowther 2026-02-2015:131 reply

            That is the most cursed description I have seen on how defer works. Ever.

            • By sparkie 2026-02-2015:32

              This is how it would look with explicit labels and comefrom:

                  puts("foo");
                  before_defer0:
                  comefrom after_defer1;
                  puts("bar");
                  after_defer0:
                  comefrom before_defer0;
                  puts("baz");
                  before_defer1:
                  comefrom before_ret;
                  puts("qux");
                  after_defer1:
                  comefrom before_defer1;
                  puts("corge");
                  before_ret:
                  comefrom after_defer0;
                  return;
              
              ---

              `defer` is obviously not implemented in this way, it will re-order the code to flow top-to-bottom and have fewer branches, but the control flow is effectively the same thing.

              In theory a compiler could implement `comefrom` by re-ordering the basic blocks like `defer` does, so that the actual runtime evaluation of code is still top-to-bottom.

      • By fauigerzigerk 2026-02-208:541 reply

        But of course what you call "surprising" and "hidden" is also RAII's strength.

        It allows library authors to take responsibility for cleaning up resources in exactly one place rather than forcing library users to insert a defer call in every single place the library is used.

    • By throwaway27448 2026-02-2010:022 reply

      This certainly isn't RAII—the term is quite literal, Resource Acquisition Is Initialization, rather than calling code as the scope exits. This is the latter of course, not the former.

      • By mort96 2026-02-2010:092 reply

        People often say that "RAII" is kind of a misnomer; the real power of RAII is deterministic destruction. And I agree with this sentiment; resource acquisition is the boring part of RAII, deterministic destruction is where the utility comes from. In that sense, there's a clear analogy between RAII and defer.

        But yeah, RAII can only provide deterministic destruction because resource acquisition is initialization. As long as resource acquisition is decoupled from initialization, you need to manually track whether a variable has been initialized or not, and make sure to only call a destruction function (be that by putting free() before a return or through 'defer my_type_destroy(my_var)') in the paths where you know that your variable is initialized.

        So "A limited form of RAII" is probably the wrong way to think about it.

        • By throwaway27448 2026-02-2010:16

          > and make sure to...call a destruction function

          Which removes half the value of RAII as I see it—needing when and to know how to unacquire the resource is half the battle, a burden that using RAII removes.

          Of course, calling code as the scope exits is still useful. It just seems silly to call it any form of RAII.

        • By usrnm 2026-02-2010:381 reply

          In my opinion, it's the initialization part of RAII which is really powerful and still missing from most other languages. When implemented properly, RAII completely eliminates a whole class of bugs related to uninitialized or partially initialized objects: if all initialization happens during construction, then you either have a fully initialized correct object, or you exit via an exception, no third state. Additionaly, tying resources to constructors makes the correct order of freeing these resources automatic. If you consume all your dependencies during construction, then destructors just walk the dependency graph in the correct order without you even thinking about it. Agreed, that writing your code like this requires some getting used to and isn't even always possible, but it's still a very powerful idea that goes beyond simple automatic destruction

          • By mort96 2026-02-2010:502 reply

            This sounds like a nice theoretical benefit to a theoretical RAII system (or even a practical benefit to RAII in Rust), but in C++, I encounter no end of bugs related to uninitialized or partially initialized objects. All primitive types have a no-op constructor, so objects of those types are uninitialized by default. Structs containing members of primitive types can be in partially initialized states where some members are uninitialized because of a missing '= 0'.

            It's not uncommon that I encounter a bug when running some code on new hardware or a new architecture or a new compiler for the first time because the code assumed that an integer member of a class would be 0 right after initialization and that happened to be true before. ASan helps here, but it's not trivial to run in all embedded contexts (and it's completely out of the question on MCUs).

            • By friendzis 2026-02-2015:38

              I think you are both right, to some degree.

              It's been some since I have used C++, but as far as I understand it RAII is primarily about controlling leaks, rather than strictly defined state (even if the name would imply that) once the constructor runs. The core idea is that if resource allocations are condensed in constructors then destructors gracefully handle deallocations, and as long you don't forget about the object (_ptr helpers help here) the destructors get called and you don't leak resources. You may end up with a bunch of FooManager wrapper classes if acquisition can fail (throw), though. So yes, I agree with your GP comment, it's the deterministic destruction that is the power of RAII.

              On the other hand, what you refer to in this* comment and what parent hints at with "When implemented properly" is what I have heard referred to (non English) type totality. Think AbstractFoo vs ConcreteFoo, but used not only for abstracting state and behavior in class hierarchy, but rather to ensure that objects are total. Imagine, dunno, database connection. You create some AbstractDBConnection (bad name), which holds some config data, then the open() method returns OpenDBCOnnection() object. In this case Abstract does not even need to call close() and the total object can safely call close() in the destructor. Maybe not the best example. This avoids resources that are in an undefined state.

            • By usrnm 2026-02-2010:551 reply

              You're talking about the part of C++ that was inherited from C. Unfortunately, it was way too late to fix by the time RAII was even invented

              • By mort96 2026-02-2011:011 reply

                And the consequence is that, at least in C++, we don't see the benefit you describe of "objects can never be in an uninitialized or partially-initialized state".

                Anyway, I think this could be fixed, if we wanted to. C just describes the objects as being uninitialized and has a bunch of UB around uninitialized objects. Nothing in C says that an implementation can't make every uninitialized object 0. As such, it would not harm C interoperability if C++ just declared that all variable declarations initialize variables to their zero value unless the declaration initializes it to something else.

                • By oasisaimlessly 2026-02-2015:311 reply

                  It's possible to fix this in application code with a Primitive<T> or NoDefault<T> wrapper that acts like a T, except doesn't have a default constructor. Use Primitive<int> wherever you'd use int that it matters (e.g. struct fields), and leaving it uninitialized will be a compiler error.

                  • By mort96 2026-02-2016:01

                    Yea no. I'm not gonna do that.

      • By ceteia 2026-02-2010:11

        [dead]

    • By usrnm 2026-02-2010:231 reply

      To be fair, RAII is so much more than just automatic cleanup. It's a shame how misunderstood this idea has become over the years

      • By randusername 2026-02-2012:491 reply

        Can you share some sources that give a more complete overview of it?

        I got out my 4e Stroustrup book and checked the index, RAII only comes up when discussing resource management.

        Interestingly, the verbatim introduction to RAII given is:

        > ... RAII allows us to eliminate "naked new operations," that is, to avoid allocations in general code and keep them buried inside the implementation of well-behaved abstractions. Similarly "naked delete" operations should be avoided. Avoiding naked new and naked delete makes code far less error-prone and far easier to keep free of resource leaks

        From the embedded standpoint, and after working with Zig a bit, I'm not convinced about that last line. Hiding heap allocations seems like it make it harder to avoid resource leaks!

        • By xerokimo 2026-02-217:26

          > Hiding heap allocations seems like it make it harder to avoid resource leaks!

          Because types come in constructor / destructor pairs. When creating variables, you're forced to invoke a constructor, and when an the object's lifetime ends, the compiler will insert a destructor call for you. If you allocate on construction and de-allocate on destruction, it'll be very hard for the leak to happen because you can't forget to call the destructor

    • By omoikane 2026-02-2017:00

      > with extra braces

      The extra braces appear to be optional according to the examples in https://www.open-std.org/JTC1/SC22/WG14/www/docs/n3734.pdf (see pages 13-14)

  • By LexiMax 2026-02-205:494 reply

    A long overdue feature.

    Though I do wonder what the chances are that the C subset of C++ will ever add this feature. I use my own homespun "scope exit" which runs a lambda in a destructor quite a bit, but every time I use it I wish I could just "defer" instead.

    • By pjmlp 2026-02-208:571 reply

      Never, you can already do this with RAII, and naturally it would be yet another thing to complain about C++ adding features.

      Then again, if someone is willing to push it through WG21 no matter what, maybe.

      • By mort96 2026-02-209:252 reply

        C++ implementations of defer are either really ugly thanks to using lambdas and explicitly named variables which only exist to have scoped object, or they depend on macros which need to have either a long manually namespaced name or you risk stepping on the toes of a library. I had to rename my defer macro from DEFER to MYPOROGRAM_DEFER in a project due to a macro collision.

        C++ would be a nicer language with native defer. Working directly with C APIs (which is one of the main reasons to use C++ over Rust or Zig these days) would greatly benefit from it.

        • By pjmlp 2026-02-209:402 reply

          Because they are all the consequence of holding it wrong, avoiding RAII solutions.

          Working with native C APIs in C++ is akin to using unsafe in Rust, C#, Swift..., it should be wrapped in type safe functions or classes/structs, never used directly outside implementation code.

          If folks actually followed this more often, there would be so much less CVE reports in C++ code caused by calling into C.

          • By LexiMax 2026-02-2017:181 reply

            > Because they are all the consequence of holding it wrong, avoiding RAII solutions.

            The reason why C++ is as popular as it is is in large part due to how easy it is to upgrade an existing C codebase in-place. Doing a complete RAII rewrite is at best a long term objective, if not often completely out of the question.

            Acknowledging this reality means giving affordances like `defer` that allow upgrading C codebases and C++ code written in a C style easier without having to rewrite the universe. Because if you're asking me to rewrite code in a C++ style all in one go, I might not pick C++.

            EDIT: It also occurs to me that destructors also have limitations. They can't throw, which means that if you encounter an issue in a dtor you often have to ignore it and hope it wasn't important.

            I ran into this particular annoyance when I was writing my own stream abstractions - I had to hope that closing the stream in the dtor didn't run into trouble.

            • By pjmlp 2026-02-2020:01

              You can use a function try block on the destructor, additionally thanks to C++ metaprogramming capabilities, many of these handler classes can be written only once and reused across multiple scenarios.

              Yes, unfortunely that compatibility is also the Achilles hill of C++, so many C++ libraries that are plain old C libraries with extern "C { .... } added in when using a C++ compiler, and also why so many CVEs keep happening in C++ code.

          • By mort96 2026-02-209:471 reply

            If I'm gonna write RAII wrappers around every tiny little thing that I happen to need to call once... I might as well just use Rust and make the wrappers do FFI.

            If I'm constructing a particular C object once in my entire code base, calling a couple functions on it, then freeing it, I'm not much more likely to get it right in the RAII wrapper than in the one place in my code base I do it manually. At least if I have tools like defer to help me.

            • By feelamee 2026-02-2013:521 reply

              if you do it once - why do you care about "ugly" scope_exit? btw, writing such wrappers is easy and does not require a lot of code.

              • By mort96 2026-02-2014:141 reply

                What do you mean with '"ugly" scope_exit'?

                Do you mean why I care that I have to call the free function at every exit point of the scope? That's easy: because it's error prone. Defer is much less error prone.

                • By feelamee 2026-02-2117:57

                  your words... > C++ implementations of defer are either really ugly

                  I agree with @pjmlp - you need to write wrappers around the C api.

                  But if you.. > If I'm gonna write RAII wrappers around every tiny little thing that I happen to need to call once

                  use them just once.. so, why care about ugliness, just write ugly code just once? Code can't be perfect.

        • By leni536 2026-02-2010:071 reply

          Not to mention that the `scope_success` and `scope_failure` variants have to use `std::uncaught_exceptions()`, which is hostile to codegen and also has other problems, especially in coroutines. C++ could get exception-aware variants of language defer.

          • By layer8 2026-02-2014:491 reply

            What C++ really needs is an automated way to handle exceptions in destructors, similar to how Java does in its try-with-resources finally blocks.

            • By pjmlp 2026-02-2015:591 reply

              While not automated, you can make use of function-try-blocks, e.g.:

                  struct Example {
                      Example() = default;
              
                      ~Example()
                      try {
                      // elease resources for this instance
                      } catch (...) {
                          // take care of what went wrong in the whole destructor call chain
                      }
                  };
              
              -- https://cpp.godbolt.org/z/55oMarbqY

              Now with C++26 reflection, one could eventually generate such boilerplate.

              • By layer8 2026-02-2016:131 reply

                What I’m thinking of is that the C++ exception runtime would attach exceptions from destructors to any in-flight exception, forming an exception tree, instead of calling std::terminate. (And also provide an API to access that tree.) C++ already has to handle a potentially unlimited amount of simultaneous in-flight exceptions (nested destructor calls), so from a resource perspective having such a tree isn’t a completely new quality. In case of resource exhaustion, the latest exception to be attached can be replaced by a statically allocated resources_exhausted exception. Callbacks like the old std::unexpected could be added to customize the behavior.

                The mechanism in Java I was alluding to is really the Throwable::addSuppressed method; it isn’t tied to the use of a try-block. Since Java doesn’t have destructors, it’s just that the try-with-resources statement is the canonical example of taking advantage of that mechanism.

                • By pjmlp 2026-02-2020:03

                  I see, however I don't envision many folks on WG21 votting that in.

    • By anilakar 2026-02-208:261 reply

      Various macro tricks have existed for a long time but nobody has been able to wrap the return statement yet. The lack of RAII-style automatic cleanups was one of the root causes for the legendary goto fail;[1] bug.

      [1] https://gotofail.com/

      • By uecker 2026-02-209:141 reply

        I do not see how defer would have helped in this case.

        • By Davidbrcz 2026-02-2010:093 reply

          People manually doing resource cleanup by using goto.

          I'm assuming that using defer would have prevented the gotos in the first case, and the bug.

          • By anilakar 2026-02-2010:261 reply

            To be fair, there were multiple wrongs in that piece of code: avoiding typing with the forward goto cleanup pattern; not using braces; not using autoformatting that would have popped out that second goto statement; ignoring compiler warnings and IDE coloring of dead code or not having those warnings enabled in the first place.

            C is hard enough as is to get right and every tool and development pattern that helps avoid common pitfalls is welcome.

            • By mort96 2026-02-2010:411 reply

              The forward goto cleanup pattern is not something "wrong" that was done to "avoid typing". Goto cleanup is the only reasonable way I know to semi-reliably clean up resources in C, and is widely used among most of the large C code bases out there. It's the main way resource cleanup is done in Linux.

              By putting all the cleanup code at the end of the function after a cleanup label, you have reduced the complexity of resource management: you have one place where the resource is acquired, and one place where the resource is freed. This is actually manageable. Before you return, you check every resource you might have acquired, and if your handle (pointer, file descriptor, PID, whatever) is not in its null state (null pointer, -1, whatever), you call the free function.

              By comparison, if you try to put the correct cleanup functions at every exit point, the problem explodes in complexity. Whereas correctly adding a new resource using the 'goto cleanup' pattern requires adding a single 'if (my_resource is not its null value) { cleanup(my_resource) }' at the end of the function, correctly adding a new resource using the 'cleanup at every exit point' pattern requires going through every single exit point in the function, considering whether or not the resource will be acquired at that time, and if it is, adding the cleanup code. Adding a new exit point similarly requires going through all resources used by the function and determining which ones need to be cleaned up.

              C is hard enough as it is to get right when you only need to remember to clean up resources in one place. It gets infinitely harder when you need to match up cleanup code with returns.

              • By mananaysiempre 2026-02-2013:451 reply

                In theory, for straight-line code only, the If Statement Ladder of Doom is an alternative:

                  int ret;
                  FILE *fp;
                  if ((fp = fopen("hello.txt", "w")) == NULL) {
                      perror("fopen");
                      ret = -1;
                  } else {
                      const char message[] = "hello world\n";
                      if (fwrite(message, 1, sizeof message - 1, fp) != sizeof message - 1) {
                          perror("fwrite");
                          ret = -1;
                      } else {
                          ret = 0;
                      }
                  
                      /* fallible cleanup is unpleasant: */
                      if (fclose(fp) < 0) {
                          perror("fclose");
                          ret = -1;
                      }   
                  }
                  return ret;
                
                It is in particular universal in Microsoft documentation (but notably not actual Microsoft code; e.g. https://github.com/dotnet/runtime has plenty of cleanup gotos).

                In practice, well, the “of doom” part applies: two fallible functions on the main path is (I think) about as many as you can use it for and still have the code look reasonable. A well-known unreasonable example is the official usage sample for IFileDialog: https://learn.microsoft.com/en-us/windows/win32/shell/common....

          • By uecker 2026-02-2010:21

            I don't see this. The problem was a duplicate "goto fail" statement where the second one caused an incorrect return value to be returned. A duplicate defer statement could directly cause a double free. A duplicate "return err;" statement would have the same problem as the "goto fail" code. Potentially, a defer based solution could eliminate the variable for the return code, but this is not the only way to address this problem.

          • By mort96 2026-02-2010:141 reply

            Is that true though?

            Using defer, the code would be:

                if ((err = SSLHashSHA1.update(&hashCtx, &signedParams)) != 0)
                    return err;
                    return err;
            
            This has the exact same bug: the function exits with a successful return code as long as the SHA hash update succeeds, skipping further certificate validity checks. The fact that resource cleanup has been relegated to defer so that 'goto fail;' can be replaced with 'return err;' fixes nothing.

            • By anilakar 2026-02-2010:332 reply

              It would have resulted in an uninitialized variable access warning, though.

              • By uecker 2026-02-2010:41

                I don't think so. The value is set in the assignment in the if statement even for the success path. With and without defer you nowadays get only a warning due to the misleading indentation: https://godbolt.org/z/3G4jzrTTr (updated)

              • By mort96 2026-02-2010:42

                No it wouldn't. 'err' is declared and initialized at the start of the function. Even if it wasn't initialized at the start, it would've been initialized by some earlier fallible function call which is also written as 'if ((err = something()) != 0)'

    • By surajrmal 2026-02-207:58

      In many cases that's preferred as you want the ability to cancel the deferred lambda.

    • By mwkaufma 2026-02-2014:03

      Just hope those lambdas aren't throwing exceptions ;)

  • By jonhohle 2026-02-206:045 reply

    It’s pedantic, but in the malloc example, I’d put the defer immediately after the assignment. This makes it very obvious that the defer/free goes along with the allocation.

    It would run regardless of if malloc succeeded or failed, but calling free on a NULL pointer is safe (defined to no-op in the C-spec).

    • By flakes 2026-02-207:351 reply

      I'd say a lot of users are going to borrow patterns from Go, where you'd typically check the error first.

          resource, err := newResource()
          if err != nil {
              return err
          }
          defer resource.Close()
      
      IMO this pattern makes more sense, as calling exit behavior in most cases won't make sense unless you have acquired the resource in the first place.

      free may accept a NULL pointer, but it also doesn't need to be called with one either.

      • By maccard 2026-02-208:402 reply

        This example is exactly why RAII is the solution to this problem and not defer.

        • By mort96 2026-02-209:13

          I love RAII. C++ and Rust are my favourite languages for a lot of things thanks to RAII.

          RAII is not the right solution for C. I wouldn't want C to grow constructors and destructors. So far, C only runs the code you ask it to; turning variable declaration into a hidden magic constructor call would, IMO, fly in the face of why people may choose C in the first place.

        • By miguel_martin 2026-02-209:091 reply

          defer is literally just an explicit RAII in this example. That is, it's just unnecessary boiler plate to wrap the newResource handle into a struct in this context.

          In addition, RAII has it's own complexities that need to be dealt with now, i.e. move semantics, which obviously C does not have nor will it likely ever.

          • By maccard 2026-02-2011:23

            > RAII has it's own complexities that need to be dealt with now, i.e. move semantics, which obviously C does not have nor will it likely ever.

            In the example above, the question of "do I put defer before or after the `if err != nil` check" is deferred to the programmer. RAII forces you to handle the complexity, defer lets you shoot yourself in the foot.

    • By masklinn 2026-02-207:44

      It seems less pedantic and more unnecessarily dangerous due to its non uniformity: in the general case the resource won’t exist on error, and breaking the pattern for malloc adds inconsistency without any actual value gain.

    • By mort96 2026-02-2011:06

      Free works with NULL, but not all cleanup functions do. Instead of deciding whether to defer before or after the null check on a case-by-case basis based on whether the cleanup function handles NULL gracefully, I would just always do the defer after the null check regardless of which pair of allocation/cleanup functions I use.

    • By krautsauer 2026-02-2010:46

      3…2…1… and somebody writes a malloc macro that includes the defer.

HackerNews