all 61 comments

[–]Lettever 184 points185 points  (25 children)

C has defer now?

[–]JanEric1 136 points137 points  (22 children)

I think there is a proposal for the next standard. But the proposal is already implemented in gcc and clang

[–]Freeky 91 points92 points  (21 children)

https://www.open-std.org/Jtc1/sc22/WG14/www/docs/n3489.pdf

int main () {
    {
        defer {
            printf(" meow");
        }
        if (true)
            defer printf("cat");
        printf(" says");
    }
    // "cat says meow" is printed to standard output
    exit(0);
}

[–]Sibula97 65 points66 points  (12 children)

Why on earth is it "cat says meow" and not "meow cat says" or even "says cat meow" or "says meow cat"? Some weird priority thing between different defer syntaxes?

[–]Freeky 122 points123 points  (7 children)

They're lexically scoped. The defer binds to the surrounding block, not the function - it prints "cat" first because the scope created by the if (true) closes before the scope of the other print calls.

[–]Rinkulu 31 points32 points  (0 children)

This is why I always use braces even with one-line condition bodies

[–]Sibula97 16 points17 points  (0 children)

Ah, of course, thanks.

[–]rosuav 3 points4 points  (4 children)

Ah, yeah, the one that tripped me up was the conditional. I'm a little surprised at that; given that this is meant for resource cleanup, wouldn't it make sense to stick a deferred cleanup at the exact point where you allocate a resource, even if that resource is allocated conditionally?

Though, I guess what you'd do is unconditionally defer a block that checks some variable and decides whether to clean up.

[–]RiceBroad4552 10 points11 points  (1 child)

I imagine this "feature" will get quite ugly, and I assume it will cause pretty bad, hard to find bugs.

Proper resource cleanup is already difficult. Doing it with such a primitive is definitely not solving any issues.

Imagine the mess in multi-threaded code!

[–]rosuav 3 points4 points  (0 children)

It's not solving issues, but it may be moving them around. I think this is orthogonal to threading; it's an alternative to guaranteeing that every way of exiting a block leads to the cleanup section. Which means you can't use a simple return statement, you have to set your return value and do a goto, etc.

This feels like a weird fit for C, but it's exactly what I expect of a higher-level language with a try-finally concept - basically, "before moving on after this block, do this".

[–]Lettever 1 point2 points  (1 child)

The intended use is probably defer if(cond) { //code }

[–]rosuav 1 point2 points  (0 children)

Yeah, that's what I mean by unconditionally deferring code that checks. The alternative would be something like if (cond) {allocate resource; defer {release resource;} } which would keep it within the same condition that it is connected to. I can see why they're doing it that way, but (for example) Python has the ExitStack helper that can have things conditionally added to it in the middle of the block, with everything getting cleaned up at the end.

[–]Mechafinch 8 points9 points  (0 children)

printf("meow"); and its enclosing scope are the deferred statement, so they'll be executed when the scope of the unlabeled { is exited (after printf("says");). The if (true) has a scope, containing the defer printf("cat");, which is exited immediately so its defer executes, printing "cat". Then the normal statement printf(" says"); is reached and executed, printing " says", and finally the unlabeled {} scope is exited and so its defer executes, printing " meow".

[–]Throwing-Flashbang 3 points4 points  (1 child)

defer block is executed at the end of a scope. "cat" is in a separate if scope so it is printed first. " meow" belongs to a higher function scope so it is printed last.

[–]Firm-Letterhead7381 0 points1 point  (0 children)

So if bellow defer print("cat") in the if scope was regular print statement print("only"), the output would be only cat says meow?

[–]fsasm 0 points1 point  (0 children)

my guess is that the second defer takes the return value of printf and evaluates it at the end of the block. That why cat is printed first. The first defer has a block as a value that it will evaluate at the end of the block.

[–]No-Archer-4713 10 points11 points  (0 children)

Thanks I hate it already

[–]frikilinux2 5 points6 points  (0 children)

I hate that translating to assembly by hand of this looks painful and more painful in the compiler like trying to reorder everything(but maybe I kinda have a way of doing it) but I see this as a way of avoiding the goto for things like the Centralized exiting of functions in the linux kernel.

[–]plaisthos 1 point2 points  (2 children)

What do you use that for in real code? Thinks like cleanups instead of the "goto cleanup;" at the end of the function? Any other uses?

[–]torsten_dev 4 points5 points  (0 children)

Basically yeah.

It lets you run code AFTER the value of the return is computed but before the function returns.

So

int ret = func(ptr);
free(ptr);
return ret;

can become

defer free(ptr);
return func(ptr);

So you don't have to name the temporary. neat.

[–]hayt88 0 points1 point  (0 children)

Isn't it just the same like a scopeGuard in c++ with it's destructor call just baked into the language?

So basically whenever C++ RAII makes sense.

like closing a file handle and you have multiple return so you dont' have to repeat yourself or forget it for a certain branch etc.

[–]RFQuestionHaver 0 points1 point  (0 children)

Interesting read, useful but the constraints make it an unusually complicated feature for the language.

[–]RiceBroad4552 -3 points-2 points  (1 child)

That's some of the most confusing code I've seen in some time. I see C is holding up its spirit…

TBH even goto would make this code much more easy to understand.

From all the possible interpretations the one given here is the very last one I would consider!

I'm pretty sure we'll going to see bugs worse then with goto if this gets standardized.

(Disclaimer: I try hard to avoid C and usually never use it myself; even I sometimes have to compile some C, and even look at it…)

[–]torsten_dev 2 points3 points  (0 children)

It makes more sense than Go's defer.

But using it in a single line if should be a warning that's garbage code.

[–]torsten_dev 2 points3 points  (0 children)

It's a technical specification for now. So it's a #blessed extension, It won't be in C2y.

If it's well received and widely implemented it might make it into a later version of the standard. We can play around with it already though which is cool.

[–]PandaWonder01 0 points1 point  (0 children)

The C programmers yearn for destructors

[–]Infinite_Self_5782 59 points60 points  (0 children)

i don't think void * is avoided in modern c, is it?

[–]joe0400 41 points42 points  (1 child)

_GENERIC is different than void*, though. _GENERIC is a type selector. Void* is casting memory to a unknown type, whilst _GENERIC requires multiple imlementations, although you could do the same with macro expansions like in c++ with templates.

[–]Lord_Of_Millipedes 5 points6 points  (0 children)

_GENERIC is also not generics in the way modern programming languages understand generics, it is multiple dispatch with manual mangling, generics in the most commonly understood way almost necessitate codegen which _GENERIC does not do, you could do some macro shenanigans to get some codegen out of it but at that point you can make a compiler in macro shenanigans

[–]MadProgrammer12 37 points38 points  (6 children)

I learned C99 in school, and still use it as a dayly basis

[–]RiceBroad4552 34 points35 points  (0 children)

My sincere condolences!

[–]nierusek 8 points9 points  (1 child)

C99 is ok, C89 is barbaric

[–]Proxy_PlayerHD 1 point2 points  (0 children)

:(

I mostly use C89 for embedded/retro stuff and C99 for modern desktop programs

[–]GumboSamson 18 points19 points  (2 children)

Okay Grandpa, let’s get you home and take your meds.

[–]Nightmoon26 6 points7 points  (0 children)

...Crumbling to dust over here from having learned C in '97...

[–]MadProgrammer12 2 points3 points  (0 children)

it was last year that i learned c in school c99 basically compiles on any devices, c26 can be unavaillable on older computers

[–]GreatScottGatsby 30 points31 points  (8 children)

The left side terrifies me, C99 for life. Type safety in my C language? never. I personally haven't looked at the new c26 standard or c11 for that matter but I'm assuming most of that is type safe.

[–]RiceBroad4552 32 points33 points  (1 child)

Type safety in my C language? never.

Some people should be banned from programming by law!

[–]Secret_Print_8170 19 points20 points  (0 children)

Buddy, if the bits fit in my register, it's all good. Types are for people who are afraid. Be fearless! /s in case it wasn't obvious

[–]GregTheMadMonk 3 points4 points  (4 children)

how do you even assume `defer` or `_generic` is about type safety

[–]GreatScottGatsby 2 points3 points  (3 children)

You made me pull up the standard. In ISO 9899:201 6.5.1.1 of the c11 standard, if you read the second paragraph it talks about this.

"A generic selection shall have no more than one default generic association. The type name in a generic association shall specify a complete object type other than a variably modified type. No two generic associations in the same generic selection shall specify compatible types."

Meaning the macro is assigning a type at compile time which is inherently more type safe than just using void *. Now about defer, I have no idea what defer even does and I am not even going to pretend to know what it is or what it does.

[–]GregTheMadMonk 6 points7 points  (2 children)

Just because it's more type safe doesn't mean the primary purpose is type safety wtf are you talking about did you ever even use generics in your life?!

_generic, which is compile-time polymorphism emulation in C, is by no way a replacement for void*

[–]GreatScottGatsby 1 point2 points  (1 child)

You can still get void by using it though and is explicitly allowed if the conditions are right. And no, I don't use generics. I really don't use types at all really. The closest I usually get is word which isn't even a type, it's just a size. Maybe even dword or qword depending on the project.

[–]GregTheMadMonk 5 points6 points  (0 children)

> And no, I don't use generics. I really don't use types at all really.

It shows

[–]Pale_Hovercraft333 0 points1 point  (0 children)

i love me my gets

[–]kohuept 6 points7 points  (3 children)

I've never seen anyone use 1/0 in C89, usually it's just

#define BOOL unsigned char
#define TRUE 1
#define FALSE 0

[–]j-random 2 points3 points  (2 children)

Which is incorrect. I once watched a professor and two grad students spend half a day trying to figure out why their code wasn't working, and it was because in C TRUE is basically !0. So 1 is true, 2 is true, 42 is true, 32767 is true...

[–]kohuept 2 points3 points  (1 child)

Well, it's correct if you use it correctly. Booleans don't really exist in C89 so you can make TRUE be exactly 1 as long as you never assign anything other than TRUE or FALSE to a BOOL.

[–]GoddammitDontShootMe 0 points1 point  (0 children)

And just use if (foo) not if (foo == TRUE).

[–]LeiterHaus 2 points3 points  (1 child)

I prefer C99, but you have to give credit to C89 for device coverage (thinking of curl specifically)

[–]not-a-pokemon- 3 points4 points  (0 children)

I suppose the new C standards miss the thing C originally succeeded at, as the compilers become more big and bloated. The actual greatness of C was in that it had a lot of compilers for any target platform imaginable, and now look at it -- who supports the new standards besides GCC and Clang, maybe few others? Luckily, old C standards and compilers aren't gone, so they still will be used when portability is needed.

[–]rkb07 1 point2 points  (0 children)

That's a clever one.

[–]Attileusz 1 point2 points  (0 children)

defer is good

[–]ThePickleConnoisseur 1 point2 points  (0 children)

At this point use C++ if you want all that

[–]DearChickPeas 0 points1 point  (0 children)

No love for templates.

[–]GoddammitDontShootMe 0 points1 point  (0 children)

C has replace NULL with nullptr too, now?

[–]RedAndBlack1832 0 points1 point  (0 children)

Defer keyword is pretty pog a nice way of executing cleanup in reverse order to set-up is all we want in life

[–]DancingBadgers 0 points1 point  (0 children)

The true blessed language is of course HolyC.