News for hackers. Stuff that matters.
Re:C: Simple Defer, Ready to Use by Night_Thastus on Monday January 06, 2025 @02:47PM (#42614760)
I really, really do not like what that could do to the readability of code if it was used liberally.

It's like GOTOs, but worse, because it's not as visible.

C++'s destructors feel like a better/more explicit way to handle these sorts of problems.

Re:C: Simple Defer, Ready to Use by accelbred on Monday January 06, 2025 @03:00PM (#42614936)
Destructors are hidden control flow, and can be non-obvious for structs from other files. I find they make code significantly harder to follow than in plain C. Defer does not have the problem, as all the logic in your function is explicitly there.
Re:C: Simple Defer, Ready to Use by quelsolaar on Monday January 06, 2025 @05:15PM (#42616614)
Not right there, some other place in the function. Also people will start adding defer in macros and then things will go sideways.
Re:C: Simple Defer, Ready to Use by jefbyokyie on Monday January 06, 2025 @07:07PM (#42617640)
> Not right there, some other place in the function.

Exactly. Both C++ RAII (constructors/destructors) and C23 defer are awful. They make the behavior implicit; in effect they tie an indefinitely large (and growing) baggage to scope termination without the source code reflecting that baggage.

Cascading gotos (or the arrow pattern) are much better. Cleanup code execution is clearly reflected by location in the source code; the exit path can be easily stepped through in a debugger.

Re:C: Simple Defer, Ready to Use by cryptonector on Monday January 06, 2025 @03:45PM (#42615541)
> I really, really do not like what that could do to the readability of code if it was used liberally.

> It's like GOTOs, but worse, because it's not as visible.

> C++'s destructors feel like a better/more explicit way to handle these sorts of problems.

But what C++ gives you is the same thing:

> It's like GOTOs, but worse, because it's not as visible.

!

The whole point of syntactic sugar is for the machinery to be hidden, and generated assembly will generally look like goto spaghetti even when your code doesn't.

What this implementation of defer does under the covers is not interesting unless you're trying to make it portable (e.g., to older MSVC versions that don't even support C99 let alone C23 or GCC local function extensions) or efficient (if this one isn't).

Re:C: Simple Defer, Ready to Use by jefbyokyie on Monday January 06, 2025 @07:08PM (#42617648)
> The whole point of syntactic sugar is for the machinery to be hidden

And when the machinery fails, you'll not only have the machinery to debug, but the syntactic sugar too.

Re:C: Simple Defer, Ready to Use by restalis on Monday January 06, 2025 @08:04PM (#42618063)
Still, the machinery gets more eyeballs and scrutiny to how it works than what some Joe's given codebase does, so chances are that the cause of failure will be somewhere on Joe's side. Then, the code will still need to be debugged, and you'll be glad having to deal with well-known implementations of DRY rule under "syntactic sugar" category instead of whatever Joe happened to come up with instead. Or, maybe there was no DRY rule in Joe's mind and therefore there will be a lot more code to maintain and debug.
Re:C: Simple Defer, Ready to Use by cryptonector on Tuesday January 07, 2025 @12:09AM (#42619470)
Yeah, compiler bugs happen.
Re:C: Simple Defer, Ready to Use by kubb on Monday January 06, 2025 @03:56PM (#42615694)
How do you write C code that needs to do this (set up several resources, and clean only some of them up depending on where the function returns) so that it’s easy to follow?
Re:C: Simple Defer, Ready to Use by jefbyokyie on Monday January 06, 2025 @07:23PM (#42617748)
When constructing an object in C, we may need some permanent sub-objects, and some temporary objects.

If we only need permanent sub-objects, then we set those up gradually, and build an error path in reverse order (with gotos or with the arrow pattern); upon success, the sub-objects' ownership is transferred to the new super-object, and the new super-object is returned, just before the error path is reached. Otherwise, the suffix of the error path that corresponds to the successful prefix of the construction path is executed (rollback). This approach cannot be rewritten with "defer" usefully. Normally you'd defer the rollback step for a sub-object immediately after its successful construction step, but this rollback step (= all rollback steps) will run upon successful exit too. So you need a separate flag for neutering all the deferred actions (all deferred actions will have to check the flag).

If we only need temporaries (and no permanent sub-objects), then (first without defer, again) we build the same code structure (using cascading gotos or the arrow pattern), but upon success, we don't return out of the middle of the function; instead, we store the new object outwards, and fall through to the rollback path. IOW, the rollback steps are used (and needed) for the successfully constructed temporaries regardless of function success or failure. This can be directly expressed with "defer"s. The problem is of course that the actual rollback execution order will not be visible in the source code; the compiler will organize it for you. I dislike that.

If we need both temporaries and permanent sub-objects, then we need the same method as with temporaries, except the rollback steps of the permanent sub-objects need to be restricted to the failure case of the function. This means that with either the cascading gotos or the arrow pattern, some teardown steps will be protected with "if"s, dependent on function return value. Not great, but still quite readable. With defer, you'll get a mix of deferred actions some of which are gated with ifs, and some others of which aren't. I find that terrible.

Re:C: Simple Defer, Ready to Use by kubb on Tuesday January 07, 2025 @01:13AM (#42619817)
So, you recommend using goto, which indeed is the only available mechanism.
Re:C: Simple Defer, Ready to Use by Someone on Tuesday January 07, 2025 @04:00AM (#42620654)
> using goto, which indeed is the only available mechanism.

There are other options, but none of them is better, IMO. You can use nested functions:

    char * p = malloc(10);
    if(p!=0) {
      doTheRealWork(p);
    };
    free(p);
In gcc, doTheRealWork can be a nested function or it you can force them to be inlined.

You can also (more readable than that first alternative, IMO) wrap code in a single-iteration for or while loop and then use break to exit it:

  char *p = null;
  for(int i=0;i==0;i=1) {
    p = malloc(10);
    if(p==0) break;
    …
  }
  free(p);
Both will get uglier if you need to free multiple resources, but will work.
Re:C: Simple Defer, Ready to Use by Analemma_ on Monday January 06, 2025 @03:28PM (#42615323)
I don't mind destructors but I don't understand your insistence that they're significantly better than defer for visibility: they're invisible and invoke a distant jump to some bit of code which might not even be in your codebase; defer is "right there" (and with proper editor support could even be made to look like it happens at the end of the function instead of where it's declared). I think they're both fine for their respective languages.
Re:C: Simple Defer, Ready to Use by skinner927 on Monday January 06, 2025 @02:51PM (#42614813)
But thats C++
Re:C: Simple Defer, Ready to Use by Night_Thastus on Monday January 06, 2025 @02:55PM (#42614875)
I am aware. I'm just pointing it out because they're similar ideas.
Re:C: Simple Defer, Ready to Use by scott_s on Monday January 06, 2025 @04:19PM (#42615994)
Look at the Linux kernel. It uses gotos for exactly this purpose, and it’s some of the cleanest C code you’ll ever read.

C++ destructors are great for this, but are not possible in C. Destructors require an object model that C does not have.

Re:C: Simple Defer, Ready to Use by up2isomorphism on Tuesday January 07, 2025 @01:02AM (#42619747)
GOTO is not necessarily a bad thing to begin with. Not to mention this does not really resemble GOTO.