As others have commented already: if you want to use C++, use C++. I suspect the majority of C programmers neither care nor want stuff like this; I still stay with C89 because I know it will be portable anywhere, and complexities like this are completely at odds with the reason to use C in the first place.

I would say the complexity of implementing defer yourself is a bit annoying for C. However defer itself, as a language feature in a C standard is pretty reasonable. It’s a very straightforward concept and fits well within the scope of C, just as it fit within the scope of zig. As long as it’s the zig defer, not the golang one…

I would not introduce zig’s errdeferr though. That one would need additional semantics changes in C to express errors.

>pretty reasonable

It starts out small. Then before you know the language is total shit. Python is a good example.

I am observing a very distinguishable phenomenon when internet makes very shallow ideas mainstream and ruin many many good things that stood the test of time.

I am not saying this is one of those instances, but what the parent comment makes sense to me. You can see another comment who now wants to go further and want destructors in C. Because of internet, such voices can now reach out to each other, gather and cause a change. But before, such voices would have to go through a lot of sensible heads before they would be able to reach each other. In other words, bad ideas got snuffed early before internet, but now they go mainstream easily.

So you see, it starts out slow, but then more and more stuff gets added which diverges more and more from the point.

I get your point, though in the specific case of defer, looks like we both agree it's really a good move. No more spaghetti of goto err_*; in complex initialization functions.

>we both agree it's really a good move

Actually I am not sure I do. It seems to me that even though `defer` is more explicit than destructors, it still falls under "spooky action at a distance" category.

I don't understand why destructors enter the discussion. This is C, there is no destructors. Are you comparing "adding destructors to C" vs "adding defer to C"?

The former would be bring so much in C that it wouldn't be C anymore.

And if your point is "you should switch to C++ to get destructors", then it seems out of topic. By very definition, if we're talking about language X and your answer is "switch to Y", this is an entirely different subject, of very few interest to people programming in X.

Sorry, I had some other thread that involved destructors in my head.

But the point is `defer` is still in "spooky action at a distance" category that I generally don't want in programming languages, especially in c.

Defer is not spooky action at a distance. It is an explicit statement that gets executed as written. Unlike (for example, a familiar feature which C doesn’t have) operator overloading… which causes code that looks like one thing (adition for example) behave like another (a function call). Defer does exactly what it says on the tin can (“move this line to the end of the scope”), just like goto does exactly what it claims to do.

Macros (in general) are way spookier than a defer statement.

>move this line to the end of the scope

Where it is invisible! What is so hard about this to understand?

>operator overloading..

Yes, but if we go by your argument, you can say it gets executed exactly as it is written. It is just that it is written (ie overloading) somewhere else ie "at distance"...just like a defer block that could be far from the end of the scope that is trigerring it

> `defer` is still in "spooky action at a distance" category

Agree, this is also why I'm a bit weary of it.

What brings me on the "pro" side is that, defer or not defer, there will need to be some kind of cleanup anyway. It's just a matter of where it is declared, and close to the acquisition is arguably better.

The caveat IMHO is that if a codebase is not consistent in its use, it could be worst.

But the real-world alternatives that people use are:

1. goto, which is "spooky action at a distance" to the nth degree. It's not even safe, you can goto anywhere, even out of scope.

2. cleanup attributes, which are not standard.

>goto, which is "spooky action at a distance" to the nth degree.

it is not.

It is, just the existence of goto makes control flow significantly harder to understand. People complain about exceptions in C++ obfuscating control flow, but then they recreate exceptions using goto. The funny thing is that exceptions are just fancy goto, the assembly is almost the same.

The bigger picture of C as a language is not that it's simple, because it's not simple at all. It's inept. It doesn't give developers the tools to write simple code. So easy things become hard, and we sort of jank together solutions that kind of work but usually don't.

I like to compare it to building a shed with power tools versus only a screwdriver. Is a screwdriver simpler than a power saw and all that? Of course. Now think about building a shed. Is it simpler to do with a screwdriver? No. It's much, much more complex. You have to develop complex processes to make that work, and it's not intuitive at all.

C is a language that already makes use of implicit control flow A LOT. I don't see defer being a problem. The irony is that if C just supported these use cases out-of-the-box, it would be simpler and easier. As a concrete example, consider polymorphism in C versus C++. Both languages can do it, but one provides the tools and one doesn't. In C++ I can go to definition, I can concretely define what polymorphism is allowed and what isn't, and the type system gives me the tools to make it safe. In C, none of that is true, so when we do polymorphism with function pointers, it's much harder to understand what's actually going on, or what could be going on.

It is not. That `GOTO` makes things hard does not shift it to some unrelated category.

> The funny thing is that exceptions are just fancy goto

Not even close.

>I like to compare it to building a shed with power tools versus only a screwdriver.

That analogy is completely wrong in the context.

> it's not intuitive at all.

It is completely intuitive, while the "modern" languages with "helpful" magic is not.

>C is a language that already makes use of implicit control flow A LOT.

> implicit

I don't think it means what you think it means. At least the examples you mention does not justify it.

None of these were arguments. You just said "nuh uh" in more words, which is really just a waste of storage.

>which is really just a waste of storage.

It is, if you choose not to think about those nuh uhs.

That comment is saying to use C++, not to add destructors to C.

Modern Python is great :shrug:

> I still stay with C89 because I know it will be portable anywhere

With respect, that sounds a bit nuts. It's been 37 years since C89; unless you're targeting computers that still have floppy drives, why give up on so many convenience features? Binary prefixes (0b), #embed, defined-width integer types, more flexibility with placing labels, static_assert for compile-time sanity checks, inline functions, declarations wherever you want, complex number support, designated initializers, countless other things that make code easier to write and to read.

Defer falls in roughly the same category. It doesn't add a whole lot of complexity, it's just a small convenience feature that doesn't add any runtime overhead.

To be honest I have similar reservations.

The one huge advantage of C is its ubiquity - you can use it on the latest shiny computer / OS / compiler as well as some obscure embedded platform with a compiler that hasn't been updated since 2002. (That's a rare enough situation to be unimportant, right? /laughs in industrial control gear.)

I'm wary of anything which fragments the language and makes it inaccessible to subsections of its traditional strongholds.

While I'm not a huge fan of the "just use Rust" attitude that crops up so often these days, you could certainly make an argument that if you want modern language features you should be using a more modern language.

(And, for the record, I do still write software - albeit recreationally - for computers that have floppy drives.)

C has its unique advantages that make some of us prefer it to C++ or Rust or other languages. But it also has some issues that can be addressed. IMHO it should evolve, but very slowly. C89 is certainly a much worse language than C99 and I think most of the changes in C23 were good. It is fine to not use them for the next two decades, but I think it is good that most projects moved on from C89 so it is also good that C99 exists even though it took a long time to be adopted. And the same will be true for C23 in the future.

I know this a four day old comment, but based on your post history I think you are probably the best person to ask to be more specific. So, you start out stating “C has its unique advantages”, an assertion I agree with but more for ‘vibes’ than because I can articulate the actual advantages (other than average compilation times). If you see this I would love to hear your list of C’s unique advantages.

My list of advantages:

  - long-term stability: the code I wrote two decades ago is still valuable to me
  - short (!) compilation times: this is a huge productivity boost
  - the language is very explicit: one can see and understand what is going on
  - mature tooling: good compilers and many other tools
  - simplicity and elegance: the language is small and not much I need to remember
  - portability: supported on many different systems
  - performance: C code can be very performance and usually it is without effort
  - lean: there is no bloat anywhere
  - interoperability: it can interoperate with everything
  - no lock-in: it does not lock you into specific frameworks or ways to do things
  - maintained using an open and (relatively) fair process: not controlled by specific companies
  - what needs to be done, can be done: there are never showstoppers

I should also talk about disadvantages and which I think are real and which are not, or not serious

  - no abstractions: i think this is completely wrong, one can build great abstractions in C
  - no package manager: IMHO languages should not have packager managers
  - difficult to learn: I think this wrong (but see below)
  - safety: partially true, but there are ways to deal with this effectively (also the advantages of alternatives are exaggerated)
  - out-dated: partially true with respect to the library
  - weird syntax: partially true, but hardly serious
  - not enough investments in tooling: rarely discussed, but I think this a problem (tools are mature, but still a lot could be improved)
  - difficult for beginners: the out-of-the-box experience is bad. one needs to learn how to do stuff in C, find good libraries, etc.

> The one huge advantage of C is its ubiquity - you can use it on the latest shiny computer / OS / compiler as well as some obscure embedded platform with a compiler that hasn't been updated since 2002.

First, for all platforms supported by mainstream compilers, which includes most of embedded systems (from 8 bit to 64 bit), this is not really a concern. You're cross-compiling on your desktop anyway. You'd need to deliberately install and use gcc from the early 1990s, but no one is forcing you. Are you routinely developing software for systems so niche that they aren't even supported by gcc?

But second, the code you write for the desktop is almost never the code you're gonna run in these environments, so why limit yourself across the board? In most embedded environments, especially legacy ones, you won't even have standard libc.

I think a lot of the really old school people don't care, but a lot of the younger people (especially those disillusioned with C++ and not fully enamored with Rust) are in fact quite happy for C to evolve and improve in conservative, simple ways (such as this one).

> still stay with C89

You're missing out on one of the best-integrated and useful features that have been added to a language as an afterthought (C99 designated initialization). Even many moden languages (e.g. Rust, Zig, C++20) don't get close when it comes to data initialization.

You mean what Ada and Modula-3, among others, already had before it came to C99?

Who cares who had it first, what matters is who has it, and who doesn't...

Apparently some do, hence my reply.

Just straight up huffing paint are we.

Explain why? Have you used C99 designated init vs other languages?

E.g. neither Rust, Zig nor C++20 can do this:

https://github.com/floooh/sokol-samples/blob/51f5a694f614253...

Odin gets really close but can't chain initializers (which is ok though):

https://github.com/floooh/sokol-odin/blob/d0c98fff9631946c11...

In general it would help if you would spend some text on describing what features of C99 are missing in other languages. Giving some code and assume that the reader will figure it out is not very effective.

As far as I can tell, Rust can do what it is in your example (which different syntax of course) except for this particular way of initializing an array.

To me, that seems like a pretty minor syntax issue to that could be added to Rust if there would be a lot of demand for initializing arrays this way.

I can show more code examples instead:

E.g. notice how here in Rust each nested struct needs a type annotation, even though the compiler could trivially infer the type. Rust also cannot initialize arrays with random access directly, it needs to go through an expression block. Finally Rust requires `..Default::default()`:

https://github.com/floooh/sokol-rust/blob/f824cd740d2ac96691...

Zig has most of the same issues as Rust, but at least the compiler is able to infer the nested struct types via `.{ }`:

https://github.com/floooh/sokol-zig/blob/17beeab59a64b12c307...

I don't have C++ code around, but compared to C99 it has the following restrictions:

- designators must appear in order (a no-go for any non-trivial struct)

- cannot chain designators (e.g. `.a.b.c = 123`)

- doesn't have the random array access syntax via `[index]`

> ...like a pretty minor syntax issue...

...sure, each language only has a handful minor syntax issues, but these papercuts add up to a lot of syntax noise to sift through when compared to the equivalent C99 code.

In Rust you can do "fn new(field: Field) -> Self { Self { field } )" This is in my experience the most common case of initializers in Rust. You don't mention one of the features of the Rust syntax, that you only have to specify the field name when you have a variable with the same name. In my experience, that reduces clutter a lot.

I have to admit, the ..Default::default() syntax is pretty ugly.

In theory Rust could do "let x: Foo = _ { field }" and "Foo { field: _ { bar: 1 } }". That doesn't even change the syntax. Its just whether enough people care.

designated initializers are really great and it's really annoying that C++ has such a crappy version of them. I wish there was a way to tell the compiler that the default value of some fields should not necessarily be 0, though it's ergonomic enough to do that anyway with a macro, since repeated values override.

i.e.

  struct foo { int a; struct { float b; const char * c } d; }; 
  #define DEFAULT_FOO  .a = 1 .d = { .b = 2.0f, .c = "hello" }
 
  ...
  struct foo bar = { DEFAULT_FOO, .a = 2 }

Not necessarily. In classic C we often build complex state machines to handle errors - especially when there are many things that need to be initialized (malloced) one after another and each might fail. Think the infamous "goto error".

I think defer{} can simplify these flows sometimes, so it can indeed be useful for good old style C.

That ship has sailed. Lots of open source C projects already use attribute((cleanup)) (which is the same thing).

Defer is a very simple feature where all code is still clearly visible and nothing is called behind your back. I write a lot of C++, and it's a vastly more complex language than "C with defer". Defer is so natural to C that all compilers have relatively broadly non-standard ways of mimicking it (e.g __attribute__((cleanup)).

If you want to write C++, write C++. If you want to write C, but want resource cleanup to be a bit nicer and more standard than __attribute__((cleanup)), use C with defer. The two are not comparable.

Isn’t goto cleanup label good enough anyway?

Goto approach also covers some more complicated cases

Then why not even better, K&R C with external assembler, that is top. /s

> external assembler

Is that supposed to exacerbate how poor that choice is. External assembly is great.

When talking about K&R C and the assembler provided by UNIX System V, yes.

Even today, the only usable Assemblers on UNIX platforms were born in PC or Amiga.