
Implementing Go's defer keyword in C++ - ingve
https://oded.blog/2017/10/05/go-defer-in-cpp/
======
saurik
This is not the same as Go's defer, as this honors scope; this _is_ similar to
D's scope guards (and hence the name of the C++ class...). In Go, when you use
defer, the code in question is deferred until the end of the function, no
matter in which scope you used the feature: this means that if you have a loop
in which you use defer, everything is deferred until well after the entire
loop finishes, not during each execution of the loop. This also means that Go
has extremely... interesting?... behavior with relation to argument capture
for the expressions used in a defer statement to make this even remotely sane.
There are reasons the people who work on Go think this makes sense, but
whether or not you agree with those semantics, the semantics implemented in
this blog post are very different.

------
oppositelock
This is a neat C++ exercise, but when you're working daily with a language and
intend to write maintainable code that's going to be around for a while, it's
best to stay within common, boring conventions of that language. In C++'s
case, just stick to explicit RAII-based guard wrappers and your life will be a
lot easier. Macro-based emulations of other language features can lead to lots
of unexpected behavior.

Sorry for pooh-poohing someone sharing some ideas, but being someone who has
to maintain long lived code in quite a few languages, code like this
immediately sets off my alarm bells.

Don't get me started on the dude who use #define magic to make C look like
Pascal.

~~~
kentonv
I disagree.

If you tell people that the only way they can do something "on scope exit" is
to write a whole class with a destructor and manually capturing variables, the
result is they won't bother. They'll just put the code at the end of the
function, and say "I'm too lazy to make this exception-safe."

This defer() macro is almost exactly what I've been using for the last 5 years
or so:

[https://github.com/capnproto/capnproto/blob/master/c++/src/k...](https://github.com/capnproto/capnproto/blob/master/c++/src/kj/common.h#L1451)

And I can say pretty confidently that it's a really good idea to have. I'm
sort of surprised, actually, that not everyone is already doing this...

------
Animats
The best solution to this problem seems to be Python's "with".

    
    
        with open(oldfile, 'r', encoding='utf-8') as infile:
            with open(newfile, 'w') as outfile:
                ....
    

The implied close will be executed on exiting the "with". "with" calls
.__enter__ on the "open" object at entrance, and .__exit__ at exit. This works
even if exit is via return or an exception.

If the implied close generates an exception, everything still works
reasonably. __exit__ has a "traceback" parameter, which indicates an exception
is in progress. An exit operation should do its closeout, then re-raise the
exception sent as the traceback, if any. This works even when a destructor
raises an exception. (A typical case is closing a network connection. If the
other end dies, close will generate an error, which should not be lost.) The
default use of "with" in Python thus comes with correct single and nested
error handling.

That's not true of RAII or Go's "defer". In C++'s destructors, you can't
return an error code, and an exception in a destructor is usually a big
problem.

It's hard, but not impossible, for a deferred call in Go to report an error.
The deferred function can muck with the parameters of the function which
called it. This only works if the deferred function was written specifically
for the context in which it was called; just deferring a "close" won't do
this. So you don't get proper error handling by default.

(Languages seem to need exceptions. Both Go and Rust started with an always-
aborts panic mechanism. That was too drastic in practice, and in both
languages, panicking was made recoverable and provided with unwinding
machinery. So both languages now have the heavy machinery for exceptions,
without the language support for them. This results in kludges to simulate
exceptions.)

~~~
RX14
Surely ruby's blocks are much better here. For one they're a much more general
construct than with, and second exception handling is done with normal
language constructs like rescue and ensure. Implementing one is simple and
obvious and is nicely contained in a single method.

For example

    
    
        File.open(args) do |file|
          # use file
        end
    

And the implementation is simply

    
    
        def self.open(*args)
          file = File.new(*args)
          begin
            yield file
          ensure
            file.close
          end
        end

~~~
lstyls
I know Python far better than Ruby, but it looks like the semantics are
equivalent, it's just that Ruby's standard `open` implementation includes the
cleanup rather than leaving it up to the caller as in Python.

~~~
masklinn
One difference is that Ruby does not need a special language
construct/protocol to do so.

An other is that Python's "context managers" are reified which means you can
pass them around until you ultimately use them. It's a tradeoff.

------
kentonv
I see a lot of people in this thread dumping on this idea, but in my opinion
as someone who has built several large systems in C++, this pattern is not
just good, it's essential. KJ (the C++ framework library built with Cap'n
Proto that I use in all my projects now) has had KJ_DEFER() since just about
the beginning, working basically exactly as described here, and I use it
constantly.

If you write C++ with exceptions, you need this. If you simply place tear-down
code at the end of the function instead, you are creating exception-unsafe
code. Of course, ideally state is encapsulated in classes with destructors and
tear-down happens there, but writing a whole class can often be overkill for a
one-off use case. If you don't have a defer macro, you're going to get lazy
and introduce bugs.

If you don't use exceptions, then you still need this, because your code
almost certainly has lots of "if (error) return nullptr;" or whatever. Without
`defer`, you need to stick the teardown code before every one of those
returns, or you need to resort to "goto fail" error handling.

~~~
quietbritishjim
> If you simply place tear-down code at the end of the function

No one is suggesting this. They are saying that you use an RAII class such
that it doesn't _need_ tear down. In the example in the article, if
std::ofstream calls close() in its destructor (which it does) then you don't
need to DEFER a call to it. If you lock a mutex, use a RAII locker class so
that you don't need to DEFER a call to unlock(). As someone once said of C++:
"my favorite feature is the close brace", because it calls destructors for all
objects on the stack, even during exception unwinding.

Of course, the article is just using RAII. But I disagree with the style;
rather than re-writing a DEFER(myfile.close()) in every single function that
uses a file object, you should make the file class have an appropriate
destructor, or at least write a wrapper that does. That way you re-use that
code rather than having to re-write it over and over.

> If you write C++ with exceptions, you need this.

You absolutely do not. You need to use the core language feature that it is
using: deterministically-called destructors.

~~~
kentonv
> No one is suggesting this.

At least one person in this thread is, in fact, suggesting this:
[https://news.ycombinator.com/item?id=15524405](https://news.ycombinator.com/item?id=15524405)

> They are saying that you use an RAII class such that it doesn't need tear
> down.

Yes, as I said in my original comment: "Of course, ideally state is
encapsulated in classes with destructors and tear-down happens there, but
writing a whole class can often be overkill for a one-off use case. If you
don't have a defer macro, you're going to get lazy and introduce bugs."

I do know how destructors work, thanks.

~~~
quietbritishjim
I guess I took your comment the wrong way. Sorry about that.

------
albinofrenchy
This seems unnecessary and possibly even harmful.

In C++ the destructor handles this as part of the lifespan of the object.
Making users of the object encapsulating the resource handle resource cleanup
is an antipattern, which is why the standard library does this for you.

~~~
alexgartrell
ScopeGuards as imagined here (Facebook's folly library has one) are near-
essential for writing reasonable systems code in C++.

    
    
       int fd = socket(...);
       if (fd < 0) return false;
       SCOPE_EXIT { ::close(fd); };
    
       if (ioctl(fd, ...) != 0) return false;
       if (ioctl(fd, ...) != 0) return false;
       ...
    
       return true;
    

You could totally just write the FdWrapper class, but then you're still a user
handling resource cleanup, but with more lines.

~~~
bodyfour
> You could totally just write the FdWrapper class

Yes, and that RAII class would be reusable all of the places you need to close
a file descriptor. It's even a handy place to hang more methods that work on
the descriptor... for instance if you wanted to abstract those ioctls.

That's why I personally never use ScopeGuards in C++. You are correct that
writing a single-use RAII class requires a few lines of extra code. However I
have found that after I write such a class I often find myself using it over
and over.

------
evincarofautumn
Side note, that DEFER macro should be variadic:

    
    
        #define DEFER(...) ScopeGuard CONCAT(_defer, __LINE__) = [&] () { __VA_ARGS__ ; }
    

Otherwise commas will be problematic—it’ll be treated as too many arguments to
the macro if they’re not wrapped in parentheses. The reason being that the
preprocessor understands parentheses as grouping delimiters, but not other
bracket characters. Also note that I changed the double underscores to a
single underscore, since the former is reserved for use by the implementation
& standard library.

It’s this type of constant minor language-lawyering and clerical work that
makes me both very good with C++ and very averse to using it anymore. (Though
to be fair this particular case is also a problem with C, which I like
nevertheless.)

------
mappu
The ScopeGuard RAII destructor will be called at the end of the scope, but
Go's defer will be called at the end of the function.

e.g. in Go can you can defer inside a for loop.

------
slavapestov
The Swift compiler uses this trick:
[https://github.com/apple/swift/blob/master/include/swift/Bas...](https://github.com/apple/swift/blob/master/include/swift/Basic/Defer.h)

------
tcbawo
It seems that this might be for only limited cases. I'm not that familiar with
Go, but I suspect that due to coroutines, the deferral may not execute for
quite some time, while in C++ this could be limited to the single-threaded
flow of the calling method (unless you manually move the scoped object out of
the call). To match Go's behavior, you would need to attach the object
lifetime to whatever future/asynch mechanism you were using. One very nice
implementation (if heavyweight) is Seastar's then chaining:
[http://docs.seastar-
project.org/master/md_doc_tutorial.html](http://docs.seastar-
project.org/master/md_doc_tutorial.html)

~~~
lucio
go's "defer" is like java/net "finally" for a try/catch enclosing the entire
function.

pro: you can push more than one "defer" in the same function.

con: "finally" puts the cleanup code at the end of the function. With "defer"
you have it scattered at the beginning.

~~~
nothrabannosir
_con: "finally" puts the cleanup code at the end of the function. With "defer"
you have it scattered at the beginning._

Quite commonly an advantage, e.g.:

    
    
        f, err := openFile('...')
        if err != nil {
            ...
        }
        defer f.Close()
    

is how it's commonly used. This makes sense because the cleanup lives next to
the initialization of the actual thing it's cleaning up. In a way it becomes
part of the initialization, itself.

Same with locks and release. Now you don't have to scan across the entire
function body to make sure your inits and cleanups match up.

~~~
albinofrenchy
There is a substantial issue with this pattern for things like files -- the
operating system imposes a max limit on the number of file descriptors you can
have open.

Defer gives no guarantees about when that thing is going to run -- just that
it is after the function returns. Because of this, you can get erratic
behavior if you open and defer closing a lot; essentially openFile just stops
working at some point.

This can happen with any limited resource and isn't theoretical; I had a
friend who ran into this in long running process with a lot of IO.

~~~
nothrabannosir
Defer statements run exactly at the moment of a function return (in lifo
order). They are equivalent to wrapping the rest of the function body in a try
/ finally. They have to, since you can change the return value in a defer (by
using named return values).

 _A "defer" statement invokes a function whose execution is deferred to the
moment the surrounding function returns, either because the surrounding
function executed a return statement, reached the end of its function body, or
because the corresponding goroutine is panicking._

[https://golang.org/ref/spec#Defer_statements](https://golang.org/ref/spec#Defer_statements)

There might have been something else going on with your friend's code. Maybe
the function he was using didn't return before running out of file
descriptors?

~~~
albinofrenchy
I somehow completely misunderstood that from the Go documentation when first
reading it; thank you for clarifying that.

There must have been some other leaking going on. He was using a largish
library when he saw the leak and eventual crash so maybe it was due to
improper pooling or C api calls? I'll have to ask him more about it.

------
junke
I am not sure capturing variables by reference implements the expected
behavior; what would the equivalent C++ code return for this Go example?

    
    
        func a() {
            i := 0
            defer fmt.Println(i)
            i++
            return
        }

~~~
JBReefer
Wouldn't this work by reference? The referenced value would be the same in
both contexts.

Honestly, am I missing something?

~~~
parenthephobia
The syntax of defer is a function call, but it isn't called like a normal
function: the arguments are evaluated at the time the defer is encountered,
and then the function is called later. That's one reason why defer is very
often called with an anonymous function, so that variables can be captured.

So, the version that prints 1 is

    
    
        func a() {
            i := 0
            defer func(){
              fmt.Println(i)
            }()
            i++
            return
        }

------
mhh__
I feel that the way this is implemented in the D programming language is more
natural (Subjective, of course): [https://tour.dlang.org/tour/en/gems/scope-
guards](https://tour.dlang.org/tour/en/gems/scope-guards)

Although this approach would probably not work like this in Go given Go's lack
of exceptions.

------
millstone
What are the advantaages/disadvantages of a function-scoped destructor like
Go's defer vs a lexically-scoped destructor like C++ offers?

------
markbnj
It's a nit I guess but I feel like RAII is a pattern, not a feature of the
language. Haven't done C++ in years but as I recall it doesn't have anything
like python's with or finally blocks, which would both serve the purpose
illustrated here. Using destructors to perform things when exiting a block is
fine, I guess, but it does introduce hidden behaviors that will need to be
well documented.

~~~
stabbles
It's a known trick also used in the standard library of C++. For instance in
multithreading

    
    
        {
        std::lock_guard<std::mutex> lock(some_mutex); 
        ...
        }
    

owns a mutex for duration of the scope. The constructor claims it, and the
destructor releases it.

But yeah, it has downsides. If you would not name the variable, and do
`std::lock_guard<std::mutex>(some_mutex);`, then its scope is limited to a
single line, and the lock is immediately released.

~~~
masklinn
And to toot its horn, Rust combines that with its ownership semantics to
create a very neat pattern: many locks have to do with _resources_ yet almost
all languages split the resource and the lock and you've got to know that you
need to acquire one lock before you use a resource without any intrinsic
relation between the two.

In Rust however sync::Mutex::new takes a value, which the lock owns, and
acquiring the lock yields a MutexGuard which acts as a reference to the
wrapped value. You _must_ acquire the lock to get access to the value (since
it's owned by the lock itself), and once the lock is released you have no way
to access the locked value (unless you created a copy).

> If you would not name the variable, and do
> `std::lock_guard<std::mutex>(some_mutex);`, then its scope is limited to a
> single line, and the lock is immediately released.

OTOH you can pretty trivially create a wrapper/extension which takes a lambda,
acquires the lock, calls the lambda then drops the lock. It could even return
the lambda's result while at it.

------
beached_whale
The macro will break with templates though. Just name it an be explicit

~~~
randyrand
why?

~~~
beached_whale
the preprocessor does not understand the commas as not being part of the macro

------
foo101
My intention is not to nitpick but I am genuinely trying to understand this
from a pedantic perspective.

> C++ has a neat feature called Resource acquisition is initialization, a.k.a
> RAII.

Is this really a feature of C++, or is this a design pattern/best practice
that developers have discovered and use to write safer C++ code?

~~~
pjmlp
It is a bit of both, as replied on siblling comments.

While RAII was coined by the C++ community, it is not unique to C++, there are
other languages that support it.

------
maxpert
Great implement a coroutine, channels, and async io using those coroutines...
WOLLAH we have a golang replacement.

~~~
gmfawcett
(It's "voilà", not "wollah". It's a borrowed French word meaning "there it
is!" or "behold!". In English, the accent is optional: "voila" means the same
thing.)

