244 comments

[ 3.1 ms ] story [ 281 ms ] thread
Just use C++. No need to proliferate the C language.
I highly doubt you can explain why.
Not the parent commenter, but namespaces alone make the switch a no-brainer. Add on top RAII, actual abstractibility (good luck implementing std::string in C properly) make it a strictly worse language.
Why ado you think namespaces make it a no brainer? I would argue the only thing I would want from C++ it's operator overloading, and I'm not even conviced about that.
Took a few years for C to adopt function prototypes from C++, one can hope that RAII makes it in less than a century
I hope it's not mandatory. I'd much prefer static analysis to tell me of use-before-acquire errors: I have a number of hot-inner-loops where initialization nukes performance.
Can you give an example of how initialization nukes performance?
In the context I'm thinking of, I have fairly sizable arrays -- up to 512 bytes; usually, only the first few bytes are used (for state tracking); an `int` tells me the high-water mark so the data is only read after being written. The amount of work int the loop is (in almost all cases) only 5 or 6 instructions before termination. Initializing 512 bytes is best case 8 ops, which is >100% overhead. The code is recursive, but bounded to a depth of 8, wity internal linkage (only two `int`s and a pointer are passed) in the tail call position, so the calling convention is just three registers. Even a compiler like q9x, or clang with no opts, produces excellent code. But, even a sniff of initialization tanks perf to the tune of 3x.
> Initializing 512 bytes

RAII in C++ does not mean that all bytes are initialized, only the ones you want. In

    struct Foo {
      int header = 0;
      float foo;
      int data[64];
      int footer;

      // just to show that having an explicit constructor / dtor does not change anything
      Foo(): footer{456} { } 
      ~Foo() { }
    };
only "header" and "footer" will be initialized: https://gcc.godbolt.org/z/4W1WzfPEv

It's not a matter of optimization, absolutely no compiler will zero-initialize unless you explicitely ask for it (or you are in a situation where the language mandates initialization, like global static in C and C++)

> It's not a matter of optimization, absolutely no compiler will zero-initialize unless you explicitely ask for it

Oh man I'm about to ruin your day. Behold the horror:

https://stackoverflow.com/questions/29765961/default-value-a...

Well don't know about the parent but you ruined my day.

Thing I keep coming back to is the proportion of effort you have to spend learning and keeping up with your tools vs the problem domain your working on. Feels like a big problem with C++ is how much energy the language itself uses up.

A friend of mine is, unlike me, really really smart. He likes golang and hates C++. And not like he hasn't spent years professionally writing C++ code. I think the advantage of golang is he can write it reflexively. So all of his attention is on the problem not the language.

Me I feel like the problem with C is not so much the language. It's that I'm always worried about stepping off a ledge. Things like defer would help. Because failing to clean up resources is a big problem with C.

But is failing to clean up transient resources that are only allocated for the duration of one function (or block) really a big problem with C? These tend to be the easiest ones to handle (but admittedly a bit annoying & verbose without defer) and spot if missing. (Also: static analyzers are relatively good at pointing out resources leaked in a single function.)

I think most of my functions that allocate a resource only release it on error; otherwise the allocated resource lives on and gets freed later by another function. Defer doesn't help at all here. It just helps with the trivial ones..

The silver lining is that really 99% of the time it just doesn't matter. I just explicitly initialize everything to zero/nullptr (unless I have a specific reason to want a different explicit value), and I only worry about looking up the particular rules if I need them in a situation where I know initialization can be a significant cost like a gigantic array. It is annoying to look up every time that happens but it's not that often.
This link is exactly what I'm saying, if you don't ask for it you don't get initialization. There are multiple ways to ask for it because the person who writes the type may eitherwant to take on the responsibility of initializing entirely by initializing on the ctor, or delegate it to the call site which can then either initialise (T t{};) or not (T t;).

The SO post looks confused ; A, B, D, E and F are the exact same case wrt the initialization of the int. It does not matter that there's a constructor or not and which shape it has, only that the variables get initialization somewhere or not (and that can be in the ctor's member init list, in the struct définition or even when creating a value if aggregate-initializing).

I'm sure you know what you're talking about wrt C++ spec, but you ignored the main point. It's easy to be confused by stuff like that. It's hard to read C++ code and know (other than intuit) what it does. For a lot of people including myself, at least.
(comment deleted)
Prototypes were in the original C standard of 1985, before first edition of The C++ Programming Language was released and long before C++ was formalized standardized.
Oy. Your history is ... confused.

While the first draft was released in 1983, the first ratified C standard was in 1989.

The first edition of "The C Programming Language" (from which I first learned the language) was published in 1978, over a decade before the language was standardized.

You are correct that C89 added function prototypes. However, contrary to your implication, C borrowed the concept and syntax from C++.

Everything I said was correct. I was a member of X3J11 (which was established in summer 1983), the C language standard, and was the first person on the planet to vote to approve it (due to alphabetical order) ... that was in 1985. It was several years before it was ratified, the delay having to do with standardization politics which required a lot of buy-in. I have no idea why you're mentioning the first edition of K&R from years earlier, which of course did not have prototypes.

There's a notable difference between C's prototypes and C++'s ... `int foo();` in C is not a prototype, whereas in C++ it's equivalent to `int foo(void);` ... C had to maintain compatibility with K&R style declarations, whereas C++ didn't.

Defer is a bad feature in Go and I'm hesitant to see it spread to other languages. It exists as a hack because error handling in Go is convoluted.
Defer is weird in Go, being tied to function scope instead of immediate block scope.
You're right, that is a better way to put it than what I said.
Function scope allows constructs like

    for _, lock := range locks {
        lock.Lock()
        defer lock.Unlock()
    }
To acquire locks in a loop and release them at the end of a function. Otherwise the previous lock would be dropped each time the loop executes.

I've never seen the advantage to having a block structured defer: it's easy to add a new function, it's not always possible to remove a block.

Named scopes would alleviate this problems, and I wish more languages would adopt them.
..Except that in C that'll turn into stack allocations and will easily segfault due to a stack overflow. So you can't actually use it like that in practice (unless you can guarantee your loop will be called a small number of times). You probably even won't notice while writing the code, and will just get random segfaults wherever you have a defer in a loop when you hit a large enough iteration count. Never mind it being very inefficient while it doesn't.
That's a good point. I was wondering about Zig's defer, which has a block scope, and I suspect it's exactly because of this issue.

Go can allocate defers on the heap, but that's a different story.

Well, we are earnestly analyzing a silly little example program, but okay :). The equivalent C code wouldn't produce stack-allocated mutexes unless that's what the programmer wanted. E.g. the POSIX pthread functions don't care where your mutexes are allocated, since they are always passed by reference.
It's not the mutexes that'd be stack-allocated, but the list of things to call back to at the end of the function. The locks list could be modified or freed by the end of the function, but something still must hold the list of things to deferred-unlock.

The pthread_cleanup_push/pthread_cleanup_pop thing presumably keeps its own heap-allocated vector, backed by malloc or something. C itself can't willy nilly heap-allocate, so that list will be on the stack. But the stack is tiny compared to how long loops can be. Hence stack overflow.

C libraries -- including the runtime implementations of features like this -- can heap allocate just fine. They just need to return pointers on the stack to the heap allocated values, either directly or indirectly (e.g. buried in a struct return value). As is always the case with C, the burden to free the memory is on the caller: no problem.

Given a possible implementation, this loopy mutex example could have a tiny stack footprint: a single pointer to the shared cleanup() function; and a single pointer to the head of a (heap allocated) linked list of pointers to mutex (i.e., the function arguments). And the function pointer would not necessarily require allocation at all, as we can statically point at the function definition here. So we are down to a single word of stack allocation.

Who's going to construct the linked list, and where does it live? That's what the parent comment is pointing out.

In the general case I see no alternative to either the compiler generating one alloca() per defer or heap allocating defer callbacks. Both are terrible solutions for C, because alloca can overflow, while heap allocations can fail with an error code and defer has no way to catch that error. Besides, C programmers just won't use the feature if it requires allocation out of performance concerns. Block-scoped defer is the only reasonable semantics.

Same question in return: who's going to alloca() or heap-allocate the defer callbacks? How is that substantively different from maintaining a linked list? As soon as compiler support is on the table -- i.e. we're not limited to using some too-clever cpp macrology and a support library -- then virtually any implementation is possible. There's obviously more than one way to do it.

> C programmers just won't use the feature if it requires allocation out of performance concerns.

I agree that many C programmers wouldn't touch the feature for performance reasons. But let's not pretend that every C program is a video driver, a AAA game or a web engine. Many, many large C programs would benefit immensely from `defer` semantics -- otherwise, why would the GCC feature exist -- and they are performance-tolerant enough that a little heap allocation would be a reasonable tradeoff for increased safety.

But I'm not really defending `defer` in the first place...

> Block-scoped defer is the only reasonable semantics.

I agree with you completely. :) I was never defending function-scoped `defer`, but answering claims about the necessity of stack allocation. There are possible defer implementations that wouldn't blow the stack: that's my only point.

> But let's not pretend that every C program is a video driver, a AAA game or a web engine.

While true, the culture in C and C++ circles pretends otherwise, hence why we have unsafe by default and safety as opt-in in those languages.

The sky would fall if we lose that 1us.

Let's hope that they sky doesn't fall. :)

It's healthy for culture-conscious programmers to reflect, now and then, on just how small a segment they represent. Cultured programming is fine, but it's like opera: the ordinary programmer recognizes a few of the tunes, but they don't sing along. It's hard to appreciate just how much uncivilized business code is out there, when nobody is getting HN likes for keeping that 1980's ERP running.

> Besides, C programmers just won't use the feature if it requires allocation out of performance concerns.

Nevermind embedded platforms where heap might be unavailable or just so scarce that its use beyond early initialization is strictly verboten. Or interrupt handlers where you simply can't call an allocator.. Block scoped defer could still be useful on such systems (e.g. with locks).

Of course. And if you were writing your embedded system in C++, you'd avoid EH and other non-zero-cost features. That doesn't mean that these features aren't useful in other contexts. I submit that the world of C and C++ programming is vastly larger and more diverse than the world of interrupt handlers and embedded systems.

...But as I pointed out in a sibling comment, I was never defending function-scoped defer. I agree with you. I was pointing out that the implementation of such a feature wouldn't require excessive stack allocation.

defer, as proposed here, isn't a library feature though, it's a part of the core language. I'd like to be able to use it, but if it can ever do a malloc (which is horrifically slow compared to not having one), it's just infeasible.

But I looked through the spec again, and it actually just says that defer outside the top level is implementation-defined, so this is irrelevant anyway.

I admit that I didn't read the actual article. :) I was using "library" in a broad sense, just to mean the runtime code that you didn't have to write yourself.
C doesn't exactly have "runtime code that you didn't have to write yourself" though. There's libc, but you can easily disable it, and having any form of defer be unavailable then is just bad. Everything else comes from your code, the headers it includes (which don't contain function definitions), and statically linked things.
Sure. And just like libc, you could easily disable (by not using) a theoretical libc_defer that provided defer semantics. Kind of like libm, you use it when you need it.

This has been a fun conversation, and I really enjoyed chatting with you about this. :) Take care.

That code is odd (though I assume idiomatic Go). I'd much rather have block scoping with something like this:

    for _, lock := range locks {
        lock.Lock()
        // Do something with lock
    }
And I don't really do Go, but in Rust, if I wanted to lock some arbitrary list of locks for a whole function, it would just be something like this at the top:

    let _ = locks.iter().map(|l| l.lock().unwrap()).collect::<Vec<_>>();
> To acquire locks in a loop and release them at the end of a function.

that looks like a super big gotcha, hasn't there been enough bugs with alloca() being called in a loop yet to show how risky and unintuitive this is ? block-scoped is explicit and explicit is good

Good implementations of defer are scope-based, so those would be clean up at the end of the loop pretty explicitly.
How often do you do something like that? Seems pretty rare. If you're managing locks maybe having an actual collection for them makes sense.

If I had to choose between allowing that pattern and allowing reasonable usage in all control constructs, I'd choose the latter.

I can see one use that when using lock ordering to prevent deadlocks (http://tutorials.jenkov.com/java-concurrency/deadlock-preven...), but then, the locks to be taken are a fixed set, and one probably would have function take_locks(lock *) and release_locks(lock *), and one could do defer release_locks.

Also, Wikipedia (https://en.wikipedia.org/wiki/Deadlock, https://en.wikipedia.org/wiki/Deadlock_prevention_algorithms) doesn’t seem to know about it. Even though I expect/guess it to be popular in embedded work, that makes me wonder whether lock ordering is used much.

Could also be an omission in Wikipedia. It has https://en.wikipedia.org/wiki/Banker%27s_algorithm, which I hadn’t heard of, and that Wikipedia says of

“In most systems, this information is unavailable, making it impossible to implement the Banker's algorithm. Also, it is unrealistic to assume that the number of processes is static since in most systems the number of processes varies dynamically”

I see this code and all I can think is that you’re going to be spending the rest of your life debugging threadsafety issues. I can contrive a case where this is useful, but where in the real world?
The semantics of block-scope defer are much simpler, easier to understand, and faster. The compiler always statically knows which defers execute at any given point, which aids optimization. With function-scope defer, the semantics are extremely dynamic, requiring bookkeeping of a runtime stack of defer thunks, and compilers have a hard time optimizing it.

Function-scope defer is something that surprises everyone I explain it to. Programmers naturally expect defer to be block-scoped.

>With function-scope defer, the semantics are extremely dynamic, requiring bookkeeping of a runtime stack of defer thunks, and compilers have a hard time optimizing it.

As far as I know, most functions have only one defer, and Go optimizes such cases quite trivially, by inlining calls to the deferred function at every function exit at compile time, without managing an additional stack. If there are several defers, then yes, the slow path is used.

How does it handle a single defer in an if?
It's very common for a function to have multiple defers ... but defers in a loop are a misuse of the feature.
You mixed up the two I guess?
What in your view makes defer a bad feature of Go? Maybe my bar is low, but each time I jump back to C I wish I had defer and end up abusing __attribute__((cleaup)) instead.
(comment deleted)
(comment deleted)
> error handling in Go is convoluted

Is it worse than in C that there is no concept at all? Is it better that everyone is on their own to do it their own way every time?

I’m not above making the eaiser, but to your point when I see their example of:

  double* q = malloc(something);
  defer [qp = &q]{ free(*qp); };
That doesn’t look like the C that I know.
The C you will get to learn is also planning to have lambdas, and they are being based on C++ design rather than Apple's blocks, so the example assumes they will also be in C2X.
Being able to close an open resource when a function returns is generally useful. It has nothing to do with error handling.
>It exists as a hack because error handling in Go is convoluted.

What's the alternative, though? Sprinkle your code with try..finally's? RAII like in C++?

Result types
Defer is used to automatically release resources on function exit, I'm not sure how result types will help here. Can you give an example?
It's an alternative to try...finally or RAII.
(comment deleted)
Context managers, or an Auto-Closable interface with syntactic support.

Also why is RAII bad? It's an awesome feature in C++.

>Also why is RAII bad? It's an awesome feature in C++

I didn't mean it's bad (I used to be a C++ developer myself and enjoyed RAII a lot), just wondering what are the alternatives that the OP doesn't consider "hacks". RAII would require to introduce constructors/destructors in the language, with all the gotchas (and probably you'll want a full-fledged OOP after that), which is apparently against Go's design principles as a simple language.

>Auto-Closable interface with syntactic support.

I don't see much difference here in practice; the whole difference is that in C#, for example, you use "using" on a whole object, while in Go it's a "defer" on a specific method of the object (or a standalone function). You are not limited to a single method and can use it on any method you deem necessary.

Auto-closeable/RAII, however, is less flexible in ad hoc situations specific to a certain function (you have to define dummy classes just to make sure a function is called no matter what), Go allows to use "defer" on a lambda. Auto-closeable also ties control flow to an object, which makes sense in an OOP-focused language, but Go isn't one.

I agree with most of your points, but I wanted to also point out that you don't need C++'s ctor/dtor spaghetti to have useful RAII: Rust achieves it without even having first-class constructors (and opt-in custom destructors via Drop).
RAII is fine. If it's a language feature, APIs can provide that to you so you don't have to write RAII wrappers all the time.

Otherwise a generic _bracket_ operator could be introduced, similar to Python's `with` statement. In C it might be a bit hard to parameterize, but even if it just requires you to use a void, it's still an improvement vs. not having anything at all. (Talking about language features so `__attribute__((__cleanup__))` doesn't count.)

Imagine something like this:

    FILE* handle = bracket(fopen("data.bin", "rb"), fclose) {
        fread(..., handle);
        return;
        fwrite(..., handle);
    } // <-- implicitly calling fclose(handle), always
Edit: After writing this sample I could even an optional `else` block in case `fopen` returned `NULL`.

Edit2: Of course, syntax fine-tuning would be welcome. For instance, `handle` should be restricted to the bracket's scope.

It’s already exists in other languages and it exists because a function could have multiple different exit points. Even if Go had different error handling there might well be different exit points. Hence why defer isn’t a Go-specific feature.
Defer has nothing to do with error handling. Defer is a great feature in Go, I've never seen someone complain about it.
Defer CAN help with error handling, but you need other language features for that. You need well defined error types or exceptions in some way or another.

Zig has errdefer which only runs if an error occurred below the statement within that scope. It allows you to always keep cleanup locally, but you can still handle errors for your business logic somewhere else.

errdefer is cool, but I don't think C should support it. Mainly because you'd need to spec errors at a language level, and doing that today is probably impossible.

The new defer proposal has the potential to make code easier to read, remove some uses of goto's and help to fix some resource leaks that are so common on old non-GC languages. Certainly a feature I'm rooting for.

The only other proposal I'm more interested in are the lambda approaches: http://www.open-std.org/jtc1/sc22/wg14/www/docs/n2890.pdf

Ugh please don't do this. Just standardize the existing __attribute__((cleanup)) mechanism which is already implemented by compilers and widely used in many free software projects.
“this feature cannot be easily lifted into C23 as a standard attribute, because the cleanup feature clearly changes the semantics of a program and can thus not be ignored.”
Then it doesn't need to use the existing attribute syntax, which requires that attributes can be ignored. They can make a new syntax.
That's a very weak objection. In any case since most programs are hiding __attribute__((cleanup)) in a macro (eg. glib's g_autoptr macro or systemd's family of _cleanup_* macros) you could use another kind of annotation.

The point here is that the C working group should be standardizing existing practice and helping the existing users of C, and not striking out making bizarre new syntax choices and keywords which are completely unproven.

So don't lift it as a standard attribute.
(comment deleted)
__attribute__((cleanup)) is ugly because for some reason, the gcc folks decided that the parameter passed to cleanup needed to be a function pointer of signature void(void**) instead of the much more sane void(void*), with no way to allow implicit casting of the parameter to a function pointer of different type, so none of the basic cleanup functions (e.g. free, fclose) work with it out of the box - you need to pollute your codebase with one wrapper for each cleanup function that you want to use.

They should have made it so that cleanup took an expression block:

    void* foo __attribute__((cleanup({ free(foo); }))) = malloc(bar);
Or at least allow a statement expression returning a compile-time known function pointer:

    void* foo __attribute__((cleanup(({ void wrapper(void** p) { free(*p); }; wrapper })))) = malloc(bar);
Sure, it still looks ugly, but at least a single generic macro would be able to clean it up and make it look better.
Oh my God, why do we put up with C at all?
What are you going to do about it? I suppose you could delete all the C software from your system. Or make a list of all the C programs that your system depends on, and persuade each of their maintainers to change languages.

Or you could just put up with C, like everybody else does, and be quietly thankful that the myriad layers of our modern, complex systems are being maintained by other people.

Sure - I agree! But, it exists and it's widely used already. The C working group should concentrate on standardizing existing practice and not start striking out on unproven and weird new syntaxes and keywords.
(comment deleted)
The "good" way to go about that would be to have a second "cleanup_value" attribute.

(Or, since the standard would be creating new names, have "cleanup" and "cleanup_ptr" instead. Assuming that this is a separate attribute namespace, which I believe it is[?])

FWIW, you do sometimes need the address of the variable. Particularly if it's some struct that is made a member of some container (e.g. linked list) temporarily - you need the original variable's address to unlink it.

I really don't like it. The lambda stuff, especially the captures that were ripped from C++, don't fit C at all. The question about whether it should be cleaned up at end-of-scope or end-of-function is too ambiguous and debated. And if I'm understanding this correctly, this is the worst part:

> This indicates that existing mechanism in compilers may have difficulties with a block model. So we only require it to be implemented for function bodies and make it implementation-defined if it is also offered for internal blocks.

How will that work?

Since variables can be scoped to blocks, and allocation scopes can be arbitrary, needing neither a function or block scope to bracket them, I don't see how that will fly either.
I was, and continue to be, very disappointed that the (to me) more natural and C-like block syntax for lambdas was rejected from C11 (http://www.open-std.org/jtc1/sc22/wg14/www/docs/n2030.pdf).
In practice those signatures can get a bit tiresome to write out, but I agree, they fit much, much better with C syntax and mindset.
Certainly, but no more tiresome than the existing C syntax for function pointers, to which they were direct analogues.
Yes, agreed absolutely. I think they'd be used more heavily because of the convenience of allowing captures, though.
I think that idea is pretty cool. I don't like the new closure pointer type, and how all functions that take function pointers need to be retrofitted to take closures as well, but I guess it's necessary. I assume closure pointers would be implemented as a double pointer, one void* to point to the captured variables in memory, and one function pointer to point to the procedure. One of the most annoying parts of C is how callback logic needs to be defined completely separately from the calling logic.
I’m not sure, im no compiler programmer but I prefer not requiring an allocator like malloc and copying data and reference counting. But I don’t know.
I usually use the single-exit-point idiom with gotos to handle cleanup. I now wonder if there is a compiler that helps you enforce that so you don’t randomly stick a return in the middle of your function.

Anyway, this would be great to have in C as it would simplify how resources are handled.

You can use the non-standard, but typically implemented, cleanup attribute today for function-scoped cleanup. I don’t know if compilers inline this kind of “destructor”. It works well — I’ve seen it used successfully at multiple employers.
Not only inline it, but also optimize it away (eg. _cleanup_free char *ptr = NULL; the cleanup path just disappears in places before ptr is assigned to a non-NULL value).
You can use the cleanup attribute and a flag to detect early exit.
If the purpose of defer is to replace the `goto single_exit`, I think the value captures are unnecessary. Any single_exit I've implemented uses the value at the time of function exit, so that I can realloc in the middle of the function.

It would be a shame if this defer was limited to function scope. It would be very useful in nested blocks as well. But, I would still appreciate it.

defer and auto are the only things I would love to see in C.

I don't mind a sane defer in C, that is something which follows scope in the same way stack allocated destructors are invoked. This follows the behaviour in other languages like Swift.

Why "on function exit" style defers - already known to be a bad idea from Go - is beyond me. Such a solution more or less requires storage of potentially unbounded allocations.

    Foo *f;
    for (int i = 0; i < very_big_value; i++) {
      f = get_foo(i);
      // BOOM
      defer return_foo(f);
    }
I would sort of understand if this was proposed for a high level language garbage collection where actual memory usage is secondary.
In this case you wrap the loop in an anonymous function if you need it to be cleaned up within the scope of the loop. Or move the functionality to another function.
Adding a statement that would require a dynamic allocation for every iteration of a loop is kind of insane for C.

It doesn't matter what the defer does, if it's allocated in a for statement and doesn't go off until the surrounding function exits, then it's going to have to stuff tons of function pointers and parameters somewhere, presumably alloca'd onto the stack over and over.

That's not C. That shouldn't be C.

There are a dozen languages for doing clever dynamic magical things in code. C is still fairly straightforward. Use one of the other languages instead of bagging down C with complexity until it turns into another difficult C++ variant over time.

> Why "on function exit" style defers - already known to be a bad idea from Go - is beyond me

Is there something you can point me to about this? I write Go professionally and from a readability and utility standpoint I really like it in common scenarios. I hadn't heard its a know bad idea and am just curious. Thanks.

I think parent means that there are languages with scope-based clean-up (e.g. in rust/c++ a value will be cleaned up at the end of the scope that contained it, so one can even create a separate block inside a function) which is a better choice than forcing people to do clean up at the end of the function.
Note that Rust isn't dropping things "at the end of the scope" but at the end of their lifetime, it's just that if you declare local variables their lifetime ends when they fall out of scope and so this often (but not always, so it's worth remembering to care about lifetimes not scopes) coincides for values in those variables.

Making things more confusing, Rust is inferring scopes you never explicitly wrote, for example Rust brings a new scope into existence whenever you declare a variable with a let statement:

  let good = something.iter().filter(is_good).count(); // good is a usize

  let good = format!("{} of them were good", good); // a String
This is fairly idiomatic Rust, whereas it would sound alarm bells in a lot of languages because their shadowing is dangerous (if you hate shadowing you can tell Rust's linter to forbid this, but may find some other people's Rust hard to read so I suggest trying to see if you can live with it instead).

Obviously that first variable named "good" is gone by the time there's a new variable named good, and so that usize was dropped (but, dropping a usize doesn't do anything interesting, beyond making life harder for a debugger on optimised code since this "variable" may never really exist in the machine code). On the other hand the String in that second variable named "good" has a potentially long lifetime, if it gets out of this local variable before the variable's scope ends.

Because Rust is tracking ownership, it will know whether the String is still in good when that scope ends (so the String gets dropped), or whether it was moved somewhere else (e.g. a big HashMap that lives long after this stack frame). Because it tracks borrowing, it will also notice if in the former case (where the String is to be dropped) there are outstanding references to that String alive anywhere. That's prohibited, the lifetime of the String ends here, so those references are erroneous, your program has an error which will be explained with perhaps a suggestion for how to fix it.

That is a common misconception. The destructor runs at the end of the scope. Try putting a println in a drop impl.
If you do this with C++ destructors, they will sure enough fire at the end of the scope. Even if your String is long gone, the destructor fires anyway, destroying... a hollowed out String left behind to satisfy the destructor.

But go ahead and try it in Rust, your print doesn't happen because nothing was actually dropped. The String was moved, and so there isn't anything to drop.

https://gist.github.com/rust-play/4bbcc2a4efb641e578e84a1962...

NieDzejkob is correct: In Rust, shadowing a variable has no effect on when the destructor of the previous value runs. Thus there's no problem with retaining a reference to the previous value:

    let a_string = String::from("foo");
    let retained_reference = &a_string;
    let a_string = String::from("bar");
    dbg!(retained_reference);
    dbg!(a_string);
Prints:

    [src/main.rs:5] retained_reference = "foo"
    [src/main.rs:6] a_string = "bar"
Similarly, "non-lexical lifetimes" have no effect on when a destructor runs. The compiler will infer short lifetimes for values that don't need to be destructed (don't implement Drop), but adding a Drop implementation to a type will force every instance's lifetime to extend to end of scope. (Though as in C++, temporaries are still destroyed at the end of the statement that created them, if they're not bound to a local variable.)

The only exception to this rule that I'm aware of is what you mentioned about move semantics: Moving a value means that its destructor will never run. That's the big difference from C++. Everything else to do with destructors is very similar, as far as I know.

To my mind, move semantics being "the only exception" is a pretty bad joke. Unlike C++ Rust's assignment semantics are moves. So, you're not opting in to anything here as with C++ move, this is just how everything works.

For example, if you were to make the second a_string mutable, and then on the next line re-assign it to yet a third string containing "quux", the "bar" string gets dropped immediately, as a consequence of move semantics again.

In C++ you'd have to go write a bunch of code to arrange that, although I believe the standard library did that work for you on the standard strings - but in Rust that's just how the language works, you assigned a new value to a_string so the previous value gets dropped.

> In C++ you'd have to go write a bunch of code to arrange that

I don't think it's quite that bad. If you define a new struct or class that follows the "Rule of Zero (or 3 or 5)", the copy-assignment and move-assignment operators will have reasonable defaults. For example, the following Rust and C++ programs make the same two allocations and two frees.

Rust:

    struct Foo {
        m: String,
    }

    fn main() {
        let mut x = Foo {
            m: "abcdefghijklmnopqrstuvwxyz".into(),
        };
        x = Foo {
            m: "ABCDEFGHIJKLMNOPQRSTUVWXYZ".into(),
        };
    }
C++:

    struct Foo {
      string m;
    };

    int main() {
      auto x = Foo{"abcdefghijklmnopqrstuvwxyz"};
      // Foo's default move-assignment operator is invoked on the temporary.
      x = Foo{"ABCDEFGHIJKLMNOPQRSTUVWXYZ"};
    }
The high-level "you assigned a new value so the previous value gets dropped" behavior is indeed what's happening, and it's automatic in most cases. But when we do voilate the Rule of Zero and override the default constructors/operators, things get quite complicated, and it's easy to make mistakes. (Also in general we often get more copies than we intended, when we're not dealing with temporaries.)

The "moves are implcit and destructive, and everything is movable" behavior in Rust is substantially simpler and often more efficient, and personally I strongly prefer it. But I'll admit that trying to contend with destructive moves without the borrow checker would probably be painful.

It seems like you could just ban that. Defers aren't typically used within loops, so why support it?
Sure they are, if there is something that is being acquired at the start of the loop and needs to get released at the end...
...of the function? You could append to a slice, after setting up a defer to free everything in the slice.

...of the loop? Call a function.

...or just make defer run at the end of the scope.
Since Microsoft has decided to sabotage C by not implementing anything that isn't in C++ already, and this will never be in C++, this feature is already dead.

Projects that target only GCC and Clang can use __attribute__((cleanup)) without waiting a decade for it.

> Since Microsoft has decided to sabotage C by not implementing anything that isn't in C++ already, and this will never be in C++, this feature is already dead.

This is no longer true: https://devblogs.microsoft.com/cppblog/c11-and-c17-standard-...

Yes it is still true, they just have decided to support newer c standards when they sabotaged c11 by making optional a fundamental feature like complex numbers:

> Support for Complex numbers is currently not planned and their absence is enforced with the proper feature test macros.

Sadly, there is no future for the C language with a corporation like them in the committee. Much better to look at Zig or Nim, they fill more or less the same space and are developed by smart and passionate people.

> A compiler that defines __STDC_IEC_559_COMPLEX__ is recommended, but not required to support imaginary numbers. POSIX recommends checking if the macro _Imaginary_I is defined to identify imaginary number support.

They have not sabotaged anything when the feature is optional to start with.

If ISO wanted everyone to actually support it, it wouldn't be optional.

I'm interested. So defer is one of the handful of achievable goals I find Zig interesting and hold some hope for it. Easy to call C from Zig or Zig from C. And Zig can also compile C and target cross-platform easily.

    const sprite_sheet_surface = c.SDL_LoadBMP("res/tile.bmp") orelse {
        c.SDL_Log("Unable to create BMP surface from file: %s", c.SDL_GetError());
        return error.SDLInitializationFailed;
    };
    defer c.SDL_FreeSurface(sprite_sheet_surface);
Somewhat related: I hacked some macro up in gnu89 C to get a Go styled defer once upon a time. I felt bad for making my compiler course instructor review that code... Very convenient, though.
> Whereas gcc’s cleanup attribute is attached to functions, POSIX’ cleanup functions and the try/finally are attached to possibly nested blocks.

> This indicates that existing mechanism in compilers may have difficulties with a block model.

Erm. No. GCC's cleanup attribute is attached to nested blocks. Whatever that means for the conclusion there.

How is a pretty basic misunderstanding like this seeping into an ISO WG document?!?

[add.:] in case anyone wants to double check: https://gist.github.com/eqvinox/c062f5a46f3a60b1151fcdf3e91a...

An individual or handful of people make a proposal, the Working Group looks at the proposal, maybe it gets revised (this is version 2). Working Groups themselves do not, on the whole, produce documents like this.

There is no ISO magic ensuring working groups are all-knowing. There's an excellent chance that no members of WG14 have a deep in-depth knowledge of GCC, or that members who did they weren't particularly interested in that part of this paper (e.g. they were already staunchly for or against, remember ISO Working Groups are democratic, a proposal does not need consensus, so a working group member who thinks your idea is inherently good or bad might just skim the introduction, and move on)

C should be retired and replaced with compiled C# (if the usage allows for Garbage Collection) or Rust (if deterministic memory management is needed). There's really no use for pulling on a dead horse.

C has had its heyday and should simply curl up and die.

this is the worst idea i read today, thanks

people don't understand what "programming" is about anymore

I think one difference between a classically trained programmer of a few decades ago and many of the programmers today who entered from javascript or bootcamps or were even self-taught is lack of understanding about all those other systems below you. For example, do you think the OP has heard of Simple Managed C?

C# is great, but it's not a systems language, it depends on piles of C/C++/etc code in order to run.

To be able to program in C# you don't need any .c file in your whole computer.

Of course you need a lot of binaries which were produced somewhere using low level languages in the process, and you probably need to comply with the C FFI to access a lot of libraries. But nothing that cannot be done with a different low level language.

You really on an entire stack that is programmed and maintained in languages like C, and to the degree that these are provided for whatever chipset you are using, yes you can code in C++. And of course you expect these libs to be regularly patched and updated, and released as new platforms become available, etc.

I'm not saying "don't code in higher level languages". I'm saying that not everyone can code in higher level languages. There is a whole stack that needs maintenance and development.

Depends on the compiler toolchain.
Not sure about bootcamps, but as a self-taught I have respect for C/C++ even if I do not use them. And even if I use Rust/whatever as self-taught I am especially humble because of all the knowledge I'm missing.
In 50 years everyone will have moved on to the hot new language, but your OS will still run, at least some, C code. Whether you like it or not C has enough inertia to last a really, really long time.
“Simple” defer feature - proceeds to implement c++ closures and semantics just to have a defer keyword which invokes them at the exit point of a scope
I don't like the nonlocality of this. When I see a closing curly brace, I want to be able to tell what execution will do when it gets there just by looking immediately before the corresponding opening curly brace, but now I'm going to have to look in the entire body of the block too. If I wanted things to be this implicit and nonlocal, then I'd have chosen C++ instead of C.
Except, when you start writing code with defers half your cleanup flags disappear, you end up with fewer branches and it makes it much harder to forget to free temporary allocations. It's really nice to look at only 1 place in code where something was allocated to ensure it also gets deallocated, instead of looking at the curly brace and then thinking: ok, what should be deallocated here, and when should it not be?
I don't like it. The great advantage of C is the very simple virtual machine and therefore very easy to reason about code. This bolting on language features is the hallmark of C++, which don't get me wrong has advantages, but in case we want the advantages we can already use C++.
The feature wouldn't introduce much complexity to the abstract machine model IMO, not any more than what cleanup labels already do.
Cleanup labels are just a conditional jump, it's explicitly in the code. The defer feature would introduce implicit guarantees by the compiler.
..And cleanup attributes aren't in C.
defer has nothing to do with C++.
C++ has try finally, which is the same thing. Instead of bloating C, anyone needing defer can use a subset of C++ that fits their needs.
C++ has no finally blocks.
> C++ has no finally blocks.

Not in the standard, but there are compiler implementations (like Microsoft’s) that have added it.

I just did a gh code search (__finally lang:c++), a hundred-ish instances shows up in ~12 repos mainly related to MS / .Net:

‎ sleuthkit/scalpel‎

‎‎ microsoft/service-fabric‎

‎ ‎dotnet/runtime‎

‎ ‎microsoft/Detours‎

‎ ‎dotnet/wpf‎

‎ ‎Chuyu-Team/VC-LTL‎

‎ ‎apache/logging-log4net‎

‎ ‎microsoft/Windows-classic-samples‎

‎ ‎microsoft/winfile‎

‎ ‎dotnet/llilc‎

‎‎ microsoft/DirectXShaderCompiler‎

‎ ‎dotnet/diagnostics‎

‎‎ SoftEtherVPN/SoftEtherVPN‎

‎ ‎microsoft/PTVS‎

‎ ‎aybe/Windows-API-Code-Pack-1.1‎

I don't think that it's in any way meaningful, it's literally unused given that it's been there for likely 20+ years.

You mix that up with RAII.
Maybe you're mixing up with SEH (Structured Exception Handling) which is a Microsoft extension to C that came even before C++
So we moved from “defer” to “using a different language, with different semantics, and use the non-standard finally”.
How does "defer" make it harder to reason about the code? Especially when the alternative is a bunch of resource-flag-tests and cleanup labels?

There is a reason why it was introduced in Go.

Because the compiler has to introduce code implicitly. The control flow is usually very explicit in C, with this feature it is less explicit.
Until a signal gets raised.
The comment was about the generated code, not about arcane OS hacks that were introduced to add asynchronous notifications to otherwise blocking OS APIs.
It is part of ISO C standard library.
So its better to make the control flow explicitly complicated with a bunch of labels and resource-flags and doing so inconsistently (because everyone has a slightly different view on how to implement it), than having the compiler do it in a consistent way?

Pragmatic point of view:

In 99% of cases, the developer doesn't care what the compiler produces and doesn't have to. A mechanism providing a complex control flow in the compiler output but is incredibly easy to read and reason about in the source code is useful in the vast majority of cases.

And for the 1% of cases where the programmer actually needs to know what the compiler produces, and / or full control over the control flow, the solution is simple: don't use `defer` and there, done, full control to the developer.

So where exactly is the downside?

You don't need to code with "a bunch of labels and resource flags", though. There are most often good ways to clean up in a reasonable way. Even the simplistic malloc()/free() works with straight line code, just initialize to NULL and you can call free() without even checking that the resource was acquired. It's also often valid to not clean up at all because the OS cleans up after process exit.

And besides, if resources aren't released in the same function you'd need to come up with a different way to clean up anyway.

> There are most often good ways to clean up in a reasonable way.

Yes, and if a defer keyword were to be introduced in C, all these reasonable ways could still be used by anyone who wants to, while we could let the compiler handle it when we don't want to, making the source easier to read and reason about.

It's slightly less explicit, but at least it's still all explicitly contained within your function. For example, you don't have to open up the definitions for a dozen different classes and read their destructors to know what happens (if anything) when your function returns. (That's the kind of stuff that can make C++ nigh impossible to reason about.)

It seems barely more implicit than expression-3 in a for loop.

I don't feel like it's going too far for C.

I dunno this capture stuff is alright but also a bit like blatant appeasement.

The point of this feature is to be non-cannonical RAII. I.e. it is about cleaning up a location. This data oriented approach is similar to how "locked data" is more correct and intuitive than "critical sections".

Now I get C being low level, so perhaps this is better, but I can't really imagine the thing I am cleaning up (as opposed to auxiliary info like an allocator to deallocate memory with) being captured by value.

BTW, speaking of moves, a "set this bool if I use this variable by value" feature would be very useful. Skip C++'s mistake and do Rust's model. Would work great with this feature.

> Now I get C being low level, so perhaps this is better, but I can't really imagine the thing I am cleaning up (as opposed to auxiliary info like an allocator to deallocate memory with) being captured by value.

I was wondering the same thing; what is the use-case for needing a capture-by-value option at scope exit?

Just make all captures a capture-by-reference and it works for all use-cases.

What an arrogant use of the word "simple" in the title.

* no decision on access to variables

* lambdas are involved at all

* "appearance in blocks other than function bodies [is] implementation-defined"

They cited D in the implementation, but then used Go as the inspiration for the feature. The answer was staring them right there in the face, scope(exit) [1]. Or, you know, they could have cited Zig for the exact syntax they wanted [2].

This feature is completely unusable.

Let me demonstrate. You can't use this inside an if statement:

    if (foo) {
        const ptr = malloc(...);
        defer [&]{ free(ptr); } // implementation defined. get fucked
    }
Furthermore, it's go-style, so the defer runs at the end of the function. This is not only implementation defined, it's full-blown undefined behavior because `ptr` goes out of scope before the defer expression runs.

Scope-based defer works great. Go-based defer is already problematic enough in Go; in C it's worse than not having defer in the language at all.

[1]: https://dlang.org/spec/statement.html#scope-guard-statement

[2]: https://ziglang.org/documentation/0.9.0/#defer

Oh come on now, it's not completely unusable, just "unusable in a ton of important use cases".
Point taken, but I will defend my claim: The few use cases where it does work are footguns because in the future, you or another collaborator will be tempted to wrap the code into a block, which is normally 100% safe for all other features of the language. It would be easy to do without thinking about it. But if you do it becomes UB as demonstrated above.

So the reasonable policy would be to use the same cleanup method everywhere, to avoid footgun firing when code is edited.

Yep, typical C decision design pattern.
(comment deleted)
The Java AutoCloseable seems like a cleaner version of the MS __try/__finally:

  try (MyCloseable c = useSomething()) {
    // stuff
  }
Why isn't this the path explored? The main difference would be not having extra nested blocks. That seems relatively minor, no? It can be improved arg list:

  try (MyCloseable c1 = useSomething(), ElseThing c2 = elseThing()) {
    // stuff
  }
This syntax relies on destructors, which C doesn't have.
One could pass in a cleanup function that will be called - but this is very similar to gcc’s cleanup attribute.
The Java implementation doesn't use destructors. The AutoCloseable interface only has the one close() method, so for C a callable function would do.
Features that seem like a good idea at the time often don't stand the test of time 20-30 years in the future. In the mid-90s Object-Oriented Programming was super-hyped so a bunch of other languages bolted on OO, such as Fortran and Ada. But now we have Go/Rust/Zig rejecting brittle OO taxonomies because you always end up having a DuckBilledPlatypus that "is a" Mammal and "is a" EggLayer.

A great strength of C is that if you want more features you just go to a subset of C++, no need to add them to C. C++ is the big, ambitious, kitchen-sink language. When C++ exists we don't need to bloat C.

Fortran was originally carefully designed so that people who aren't compiler experts can generate very fast (and easily parallelized) code working with arrays the intuitive and obvious way. But later Fortran added OO and pointers making it much harder to auto-parallelize and avoid aliasing slowdown. Now that GPUs are rising it turns out that the original Fortran model of everything-is-array-or-scalar works really well for automatically offloading to the GPU. GPUs don't like method-lookup tables, nor do they like lambdas which are equivalent to stateful Objects with a single Apply method.

Scientists are moving to CUDA now, which on the GPU side deletes all these features that Fortran was bloated with. Now nVidia offers proprietary CUDA Fortran which is much more in the spirit of original Fortran, deleting OO and pointers for code that runs on GPU. If the ISO standards committee didn't ruin ISO Fortran for scientific computing by bloating it with trendy features we could all be running ISO Fortran automatically on CPUs and GPUs with identical code (or just a few pragmas) and not be locked in to proprietary nVidia CUDA.

But GPUs are now mainly used for crypto greed instead of science for finding cancer cures or making more aerodynamic aircraft so maybe it all doesn't matter anyway.

> GPUs don't like method-lookup tables, nor do they like lambdas which are equivalent to stateful Objects with a single Apply method.

Since I tend towards read-only instance data, I often live my life considering an object to mostly be a bag of closures with a shared outer scope.

Yeah. I think I'm much less informed on this topic, but my initial thought on reading the "Rationale" section was that this sort of feature would only be helpful in cases where C offered almost no advantages over C++.
> A great strength of C is that if you want more features you just go to a subset of C++, no need to add them to C. C++ is the big, ambitious, kitchen-sink language. When C++ exists we don't need to bloat C.

This is a rationalization, and a bad one. When your solution is "just pull in another programming language", you have a problem.

"Another programming language" cannot even meaningfully exist if all programming languages are forced to have the same feature set. Should Python get C-like low-level pointer manipulation so that Python users don't need to "pull in another programming language" of C to do pointer manipulation?

C doesn't need "defer" because C programmers have managed since the 1970s to implement operating systems, compilers, interpreters, editors, etc., just fine without it. Those who want a bigger C can use C++, this pond is big enough for two fish.

> all programming languages are forced to have the same feature set

Good straw man there. Did I say all languages need to be exactly the same? This comment just looks like something you can fall back on to reject any feature addition to C. Its too bad really, as its sentiment like this that is killing the language. Many people are sick and tired of old, crusty C, where it takes close to a decade to add or change anything. I like the idea of a small, performant language, but when you put such a stranglehold on changes, you choke out most chances of innovation.

> I like the idea of a small, performant language

So the earliest C compilers were under 5000 lines of C+asm:

  https://github.com/mortdeus/legacy-cc
If you want a minimal "standard committee approved" C89 compiler then David Hanson's lcc and Fabrice Bellard's tcc both come out to over 30,000 lines. To understand C89 fully you at a minimum have to read a ~220 page (14,248 line) copy of the (draft) ANSI standard:

  http://port70.net/~nsz/c/c89/c89-draft.txt
I don't know what the smallest C23 compiler would be with all the new features since C89 added, but it's at the point where a single human can't implement a C compiler anymore. It's becoming a language only rich corporations have the wealth and power to implement and steer.
On the other hand, some features turn out to be a very good idea and do stand the test of time. Designated initializers and compound literals, introduced in C99, are perfect examples of C features that stuck and became very widespread, while keeping the spirit of the language. C shouldn't be set in stone.

The fact that goto-based solutions and a non-standard GCC extension are common methods of resource cleanup in C today seems to suggest that a standardized language construct for resource cleanup would be appreciated.

> A great strength of C is that if you want more features you just go to a subset of C++, no need to add them to C.

What is C for then? Cleanup of function-scoped resources is a major concern in every large C codebase I've seen.

It's not an major concern though.

If one has trouble writing correct cleanup code conventionally (with "goto out" and a single function exit), then allowing them to use defer will only lead to more obscure issues.

And if defer is meant to make code slimmer, it still doesn't belong to C, because it leads to implicit execution and memory/stack allocation.

C is an explicit and verbose language. What you see is what you get. This is the spirit of the language. Unlike with, say, C++ where "a + b" may actually produce kilobytes of machine code, because + just happend to be overloaded.

> If one has trouble writing correct cleanup code conventionally (with "goto out" and a single function exit), then allowing them to use defer will only lead to more obscure issues.

I've written countless functions in this style and I don't enjoy it. I think it's better than the other styles of resource cleanup in C, but it's not ideal. In this style, whenever I add a resource to a function, I have to go to the top, add the declaration (with a sentinel value,) then go to the out label, check for the sentinel value and conditionally destroy it. I'd much rather add the declaration, initialization and destruction of the resource all in one place. That would make it much harder to forget the destruction, for one thing.

> And if defer is meant to make code slimmer, it still doesn't belong to C, because it leads to implicit execution and memory/stack allocation.

I don't get the implicit execution thing, and I don't see how it's like that C++ example. The only code that executes is written in the function itself, inside the defer block.

> I've written countless functions in this style and I don't enjoy it. I think it's better than the other styles of resource cleanup in C, but it's not ideal.

I've got almost a couple decades of C behind me, and I agree. The way we handle cleanup at present is not particularly difficult, but it feels irritating. I'd imagine most C programmers agree that the goto based cleanup handlers just happen to be the best we've got, and aren't necessarily ideal.

> And if defer is meant to make code slimmer, it still doesn't belong to C, because it leads to implicit execution and memory/stack allocation.

I don't see why block scoped defer should cause any more memory or stack allocation than a goto based cleanup handler. It's just a different way to organize the source code. In some instances it might actually allow you to omit some local variables (that otherwise would have to be optimized out by the compiler).

> C is an explicit and verbose language.

It's relatively explicit, I agree. However, defer doesn't change that much. You still see exactly what code runs inside your function. The only real change is that code's location. It's not that different from putting expression-3 in your loop header and having it be evaluated implicitly when you reach the end of the body or do a continue. If you wanted to be explicit, you'd ban for loops and use gotos in a while loop to replace continue. Umm, be my guest, but I prefer the less verbose approach.

And that gets me to the second point... C can be surprisingly terse despite requiring you to be rather explicit, and that's one of the things I really like about C. If anything I'd love to see features that allow it to be even more terse.

> Unlike with, say, C++ where "a + b" may actually produce kilobytes of machine code

Oh, I agree. I really don't want tons of hidden code in C. However, the deferred block is still explicitly coded inside your function and not at all hidden from you someplace else. So it's not like you need to go spelunking through a pile of headers and class definitions to discover that there are destructors running SQL queries when your function returns.

In that respect, defer remains very explicit and transparent so I'm ok with it.

> block scoped defer

That's the thing. Block-scoped is a better option as far as the language "spirit" is concerned, but it's limiting (see below). Function-scoped is more useful, but when used in loops it may lead to unbound stack usage and that sorta goes against the rest of C, because no other _language construct_ comes with such lovely side effect.

Re: limiting - It's not uncommon for a function to need to grab some resource conditionally and then use it in the rest of the function code, e.g.

    void foo()
    {
        bar * b = NULL;
        if (x && y)
        {
            this();
            b = that();
        }
        ...
        baz(1, 2, b); // b may be null
        ...
        release(b);
    }
This can't be handled with block-scope defers. This needs function-scoped ones.

A better option would (probably) be to allow binding defers to a specific on-stack variable... but that's basically a destructor and that opens its own can of worms, not all of which as technical.

It seems a bit limiting, yes, but this does not seem like a major limitation to me. Especially if we compare it to how existing practice with goto based cleanup handlers would work in this example. It doesn't really matter that the resource was obtained in a block, the variable holding a reference is still scoped to the function body and will be checked at the end just as it would be with goto.

    void foo()
    {
        bar * b = NULL;
        defer [&]{if (b) release(b);}

        if (x && y)
        {
            this();
            b = that();
        }

        if (something_gone_wrong())
        {
            return; // no problem, b gets released if it was acquired
        }
        ...
        baz(1, 2, b); // b may be null
    }
If making the release conditional seems a bit hacky, remember that you need that sort of thing anyway for the hugely common case where you allocate & initialize a bunch of things and then let the caller keep the resources, except if there's an error.. in which case you need to clean everything up. Without some additional language features (first class error types or "error returns", then error defers?) these conditions are unavoidable.
Sticking defer under the var declaration is clever, but it doesn't look an improvement in terms of the code quality to me. It trades verbosity of the "out:" pattern for the need to register the cleanup code before the acquisition code. That's just weird. It's not complicated, just... backwards. Almost like a solution in search of a problem :)
Dunno, I feel like the goto out pattern is substantially more irritating any time you actually want to return a value from the function. I'd like to just return val instead of int val; /* ... */ ret=val; goto out;
That doesn't belong in C. C doesn't have much in the way of implicit execution. C++ does, and C++ already has destructors.