
A Defer Statement for C - pcr910303
https://github.com/moon-chilled/Defer
======
eqvinox
Sounds like you're trying to emulate the "cleanup" GCC variable attribute:

[https://gcc.gnu.org/onlinedocs/gcc/Common-Variable-
Attribute...](https://gcc.gnu.org/onlinedocs/gcc/Common-Variable-
Attributes.html)

Example use:
[https://github.com/FRRouting/frr/blob/master/lib/frr_pthread...](https://github.com/FRRouting/frr/blob/master/lib/frr_pthread.h#L218-L264)

(FRR requires a GCC/clang compatible compiler, so no fallback/workaround.)

~~~
loeg
GCC attribute cleanup is so much harder to misuse than some hodgepodge macro
construction like this. If you're a C developer, it is by far the superior
option. Clang supports it, of course.

~~~
rwmj
It's also used in several important big Linux programs so it's unlikely to be
going away. There have been efforts to get the mechanism added to the next C
standard too.

~~~
loeg
Re: going away — has GCC ever removed any relatively common attribute in the
past? They basically don't ever, to my knowledge. They haven't removed nested
functions, for example, which would probably be a good idea. (To your point,
though: important Linux programs like GRUB use nested functions.)

------
devit
Moving to C++ seems wiser than using a dubious hack like this.

~~~
earenndil
I wrote on reddit[1] about why I prefer this over c++:

> It's not a technical problem, but a social problem. Yes, I would definitely
> prefer the c++ RAII (and refcounts would be nice too). If you say 'my
> project is in c++', that sends a certain message to prospective
> contributors, about what your priorities and ideals are. It can attract
> certain kinds of contributors and discourage others. Then you have the
> problem of how to define your subset of c++. It's easy to say 'no
> exceptions, no RTTI, no STL'. But there are subtler things. As you mention,
> templates are occasionally useful. But sometimes they're completely
> superfluous. Do you allow virtual functions? Multiple inheritance? The
> answer is almost invariably 'maybe'; you have to exercise _taste_. I can do
> that by myself, for my own project. But if I want to be able to accept
> contributions from others, I need a clearer set of contribution guidelines
> than 'wherever my whimsy takes me', and for such a purpose 'whatever the c
> compiler accepts' is the best I can do.

> Also, tcc is about 10x faster than gcc and clang, which makes development a
> joy.

1:
[https://www.reddit.com/r/programming/comments/f4gb6n/i_made_...](https://www.reddit.com/r/programming/comments/f4gb6n/i_made_defer_for_c/fhqo14q/)

~~~
jcelerier
> Then you have the problem of how to define your subset of c++. It's easy to
> say 'no exceptions, no RTTI, no STL'.

how about you just don't do that like 99% of modern C++ projects and just
accept "whatever the C++ compiler accepts".

Remember that "whatever the C compiler accepts" covers most of IOCCC too.

~~~
pingyong
Because the C++ compiler accepts an absolutely insane variety of code styles.
I say this as someone who mainly writes C++. If I write my own project, or a
project where I only have to work with a small number of people all of whom I
know have extensive C++ experience, or a project that is so large that I can
afford to consistently rewrite parts, educate contributors and maintain style
guides, I choose C++. But anything that doesn't hit these bells, I'd rather
use C (or something else). People mixing completely different styles in C++ is
a nightmare.

~~~
jcelerier
> People mixing completely different styles in C++ is a nightmare.

This is really not my experience. Any decent-sized program will have parts
more in a functional style, others in a more OOP one, others in regular-typed
Stepanov bliss, others in template or constexpr metaprograms.... and this
causes zero issues in practice once people get past their assumptions about
what clean code should look like.

------
ill0gicity
This is one I put together a while back for gcc and clang:
[https://github.com/jeffwalter/defer/](https://github.com/jeffwalter/defer/).
There's no limit to the number of defers you can have. At least not one I've
run into.

~~~
earenndil
My version isn't as dependent on nonstandard extensions.

------
cryptonector
You could use a Duff's device instead of computed labels, then you'd have a
portable implementation.

~~~
electrum
That same technique is used by
[https://www.chiark.greenend.org.uk/~sgtatham/coroutines.html](https://www.chiark.greenend.org.uk/~sgtatham/coroutines.html)

~~~
cryptonector
Yes, quite! I mentioned that and async.h in issue #1. I also opened a PR with
a Duff's device version.

------
earenndil
Author of the library here; AMA.

~~~
rwmj
Why not use __attribute__((cleanup))? It's widely used in real code (eg.
systemd has been using it for years) and there's effort going on to get the
mechanism standardized in the next C standard.

~~~
Kipters
How is support across compilers? Do clang and MSVC support it?

~~~
loeg
Does MSVC support C99 yet? Much less C11 or a future C2x?

Their priority has always been C++ and for some time their stance on C was
basically "I _guess_ we're willing to compile the subset of C that is valid
C++."

------
lxe
Looks like you also have to use a custom return (`Return`) which is error-
prone.

~~~
cryptonector
You can define the macro as `return` -- it won't cause infinite recursion in
the C pre-processor, but you do have to either use the `Deferral` macro in
every function, or `#undef return` to do a plain return.

Try it. I just did, and it works.

~~~
kazinator
Problem is, other header files you might include can have inline functions in
them with returns.

Defining macros with C keyword names is not such a hot idea.

~~~
int_19h
It is expressly prohibited by the Standard, as well.

------
MaxBarraclough
For the C++ equivalent see Boost.ScopeExit

[https://www.boost.org/doc/libs/1_72_0/libs/scope_exit/doc/ht...](https://www.boost.org/doc/libs/1_72_0/libs/scope_exit/doc/html/index.html)

~~~
lyricaljoke
Or, arguably, the C++ equivalent is just the destructor. 'Defer' and
'ScopeExit' are interesting, but I'm not sure if I see the advantage over
widely understood first-class language features. If you don't want to write a
class just to get cleanup logic at scope exit, consider using std::unique_ptr
with a custom deleter -- this construct is for managing resources in a general
sense, not necessarily just memory allocation.

~~~
MaxBarraclough
> arguably, the C++ equivalent is just the destructor

You'd have to define an empty class with a destructor, then instantiate the
class at the appropriate place in your code. The point of Boost.ScopeExit is
to handle that boilerplate for you.

> I'm not sure if I see the advantage over widely understood first-class
> language features

The advantage is simply reduced boilerplate. You're right that it's 'less
standard' and more likely to baffle the reader, if they're not already
familiar with ScopeExit.

> consider using std::unique_ptr with a custom deleter -- this construct is
> for managing resources in a general sense, not necessarily just memory
> allocation

This strikes me as hijacking a memory-management facility, using it for a
different purpose than its intent. That's bad for readability. I'd prefer
either ScopeExit or the dummy object pattern I described.

~~~
pingyong
>You'd have to define an empty class with a destructor, then instantiate the
class at the appropriate place in your code. The point of Boost.ScopeExit is
to handle that boilerplate for you.

I would argue that every time you want to execute something on scope exit,
that essentially models a resource or lock of some kind. Which means the
constructor wouldn't be empty (it would acquire the resource), and writing the
class wouldn't really be "boilerplate", it would be clean design.

>This strikes me as hijacking a memory-management facility, using it for a
different purpose than its intent. That's bad for readability. I'd prefer
either ScopeExit or the dummy object pattern I described.

I think it is much better for readability, because it usually models the thing
you want to do much more precisely. Although it is a shame that only
unique_ptr is standard and not unique_handle, but you could write that
yourself relatively quickly. This case doesn't just model that you want to
execute something at the end of a scope, but that you have a handle to a
resource that you can std::move around and that will get destroyed at the
appropriate time (if you model your data correctly).

~~~
MaxBarraclough
> essentially models a resource or lock of some kind

I agree that proper RAII is generally the way to go. I think of ScopeExit as
being for those situations where you have to deal with C-style code. One way
is to wrap the C-style code in C++ and leverage C++'s RAII. The alternative is
to write C-style code yourself, but you can still use ScopeExit to ensure you
didn't miss any edge-cases where cleanup is necessary (multiple points of
return, exceptions, etc).

In this way, ScopeExit is for 'C++ as a better C' programming.

> This case doesn't just model that you want to execute something at the end
> of a scope, but that you have a handle to a resource that you can std::move
> around and that will get destroyed at the appropriate time

This is going far further down the C++ rabbit-hole than what we started with,
though. If I just want to be sure that I didn't miss any places where I need
to call _free_ (or similar) in my C-style code, ScopeExit is just the thing.

------
rkeene2
Semi-related, I built one for Tcl [0].

[0] [https://chiselapp.com/user/rkeene/repository/tcl-
defer/](https://chiselapp.com/user/rkeene/repository/tcl-defer/)

------
waynecochran
How does this compare to using RAII in C++ in terms of generated code?

~~~
MaxBarraclough
As I mentioned in another comment, it seems almost identical to
Boost.ScopeExit, [0] but with the special restriction that it causes
statements to run at the point a function returns. From the article: _it does
not care about lexical scopes_.

 _edit_ Looking at the implementation, it seems to use setjmp/longjmp on some
platforms. Nasty. I believe Boost.ScopeExit does some macro trickery to
leverage C++'s RAII, which isn't so bad.

[0]
[https://www.boost.org/doc/libs/1_72_0/libs/scope_exit/doc/ht...](https://www.boost.org/doc/libs/1_72_0/libs/scope_exit/doc/html/index.html)

------
zvrba
Uses globals. Not thread-safe in any way.

~~~
CGamesPlay
I don't think it actually does. Per the example, the `Deferral` macro
instantiates local variables which are used to do the unwinding.

~~~
zvrba
Oh, you're right. I missed \ at the end of definition of Deferral macro.

------
adamrezich
cool hack! idk about its usefulness in production but cool nonetheless

