It's almost never what you want, and it requires terrible gymnastics whenever the difference matters.
So in C++ implementing defer the right way is to just register a std::function that gets run from the class destructor. I've used it as an alternative to wrapping a resource, or timing a function call.
Only gotcha is to be careful about throwing from within the destructor. Or to put another way: don't.
scope(exit) writeln("3");
writeln("1");
writeln("2");
Output: 1
2
3I've written some functions-in-functions solely to get "scope-like" deferral. Now, so far, none of them have ever survived refactoring, in that they've always been lifted up to become their own top-level functions where defer resumes working like I wanted anyhow. But I've also got a ton of code where the difference doesn't much matter (a function with only one scope, or where defer is only on the outer-most scope anyhow), and it still seems like scope would be a better default.
class defer{public:defer(std::function<()> f):f_(std::move(f)){} ~defer(){f_();}private:std::function f_;};
Then fd = socket(…);
defer _([]{close(fd);});
But yeah, most of the time a refactor will instead create a proper wrapper class. It's rare that I want to RAII something, but only in some cases. E.g. I probably want RAII on FDs always, with proper ownership, not merely in some cases.And this is also what Go missed. I'm a big fan of RAII.
template<class Fn>
struct defer {
Fn fn;
~defer() { fn(); }
};
template<class Fn>
defer(Fn) -> defer<Fn>;
int main() {
defer _ { [] { std::cout << "exit"; }};
}
Although you might want to add the ability to dismiss() the guard.edit: a more complete implementation: https://godbolt.org/z/6675K1 . I like to name my guards. Making them anonymous is left as an exercise for the reader.
But like I said at some point, if it gets complicated, then probably the value should be properly wrapped, with actual owner semantics rather than defers.
To be clear, I don't think that chains of cleanup-labels with corresponding gotos are better. As a long-time OOP basher, I find myself using more and more explicit state structures with "object" semantics: There's a destructor that cleans the state up from every possible configuration except "uninitialized". As far as possible, objects should already be considered initialized when cleared to 0. (This idiom is called ZII, or zero-is-initialization).
A problem with defer is that if one has to split what the function does in multiple functions, defer breaks, and one needs a completely different solution (explicit state structure). Not to praise it too much but with RAII you don't pay that cost.
In other words, as so many language features, defer offers to save early typing work early, at the expense of later friction, or even of requiring a re-write, when the times comes to solidify the code structure.
Disclaimer: I've never actually typed the word defer in any programming language. :-)
Does anyone know why Go’s `defer` semantics is so unintuitive? Why is `defer` function-based rather than block-based, in a language where variables are block-scoped?
But I've also heard the opinion that some find the function based `defer` easier to reason about, so maybe that weighs in as well.
First, early versions of Go strictly required a “return” as the final statement in a function, rather than just checking that each branch terminated successfully (i.e. you couldn’t return from both branches of an if/else, you also needed a redundant return statement afterwards). So I think initially the compiler didn’t perform the kind of escape analysis that would make block-scoped “defer” easy to implement.
Second, block-scoped defer is really equivalent to RAII via constructors and destructors in C++. Go was specifically designed as an anti-C++, an attempt to return to the roots of C and improve on it, so the designers would have been very wary of copying C++ features like RAII.
I think it was a mis-step, and they should have gone with block-scoped. Other languages have copied Go’s syntax, but they’ve uniformly opted for C++’s semantics, which in this instance are much easier to reason about (definitely not always the case with C++!)
Ha! And still they managed to reimplement RAII and exceptions, only very very poorly.
For example, when writing code that performs an operation with the potential to panic, but that you want to convert to an error, something you can do is name the error return value and then modify it in the defer, after recovering from a panic, according to the cause of the panic. It's not obvious that that pattern would work well if defer could also execute on scope end - what does recover do then? Will it affect the following recovers? Also, if the function continues executing but you modified some variables, it makes it complicated to reason about what could be in those variables.
Defer running at function end is much more easier to reason about: functions execute their standard control flow until a return or panic is hit, and then defers are executed, without returning to standard control flow.
If the language only has block based defers then that's it. You have that and only that.
What if I create a resource in an if block and want to defer closing it when the function ends?
func Deferred() {
defers := []func(){}
defer func() {
for _, def := range defers {
def()
}
}
for {
cleanup, done := work()
defers = append(defers, cleanup)
if done {
break
}
}
}
Edit: this is how defers were implemented in the compiler until a few versions ago and will fall back to this if you defer in a loopI suspect the real reason is the driving force behind a great deal of the early Go 1.x design choices: making the compiler simpler. Or, if you wish: _compiler_ performance.
Go 2.x is shaping up to be a different beast, but Go 1.x had an extremely strong design philosophy favoring compiler simplicity (which often equates to compilation speed) over nearly every other concern, including runtime performance. That's exactly one of the main argument that was used against having generics: (non-erased) generics significantly improve performance, but slow down the compiler.
I'm personally very much in disagreement with this philosophy, but I don't think all these design choices are baffling or even misguided - they clearly achieve what they were set to do.
fd := OpenSocket(...);
if fd != 0 {
defer CloseSocket(fd);
}
// do stuff
For the same reason, Go's defer cannot be implemented without dynamic allocations. For example, how would you implement: func foo(int n) void {
for i := 0; i < n; i++ {
var res = bar(n);
defer fmt.Println(res);
}
}
This calls bar(0), bar(1), etc. then prints the results of bar(n - 1), bar(n - 2), etc. There is no way to implement this without some way to store the intermediate values.This is a bit similar to local variables: they have to be allocated on the heap in the general case, but Go uses escape analysis to determine which local variables can safely be allocated on the stack instead, which is much cheaper.
The official Go compiler a couple of versions back optimized this away to the obvious (if you know about compilers) mapping of region to what defers need to be run on exit and can compile them in now. If your function uses it like a stack, it'll use a stack like it used to, but it optimizes the common case of a handful of defers in the main body now.
Consider this code
fp = os.open(x) // imagine file open
defer fp.close()
fp.read()
With defer, one would think I can simply wrap this in a for-loop if I want to open and read a bunch of files. Go doesn’t promise this, but not clear until linter complains. In languages were it “ends at scope”, this is still wrong. If we wrote it as finally, dev would know finally is outside the loop or they need to wrap in another sub-scope inside the loop {}I don’t quite follow -- what would go wrong? If you put that in a loop body, won’t you get one open() and one close() per iteration, as intended?
// option 1
try {
fp = os.open(x)
fp.read()
}
finally {
fp.close()
}
// option 2
fp = os.open(x)
try {
fp.read()
}
finally {
fp.close()
}
Should we close if open fails? Maybe, maybe not, but with try/finally it is obvious which one it is doing. for /* whatever */ {
fp = os.open(x) // imagine file open
defer fp.close()
fp.read()
}
This is wrong. Go linter might tell you it is wrong, but
a) the defers are pilling up and you might run into too-many-files open
b) I can never remember if `fp` is saved in defer closure by reference or value. That is, at the end, even if inefficient, are all pending defers closing the same pointer?
Now in a language where "it ends in scope", the scope hasn't ended until the loop exists. Now in the RAII world, the `fp` being overwritten would have saved the day by automatically closing it, but we are not talking about RAII world.With finally, things are clear, I think.
No, I don’t think that’s correct, in any mainstream language. Each iteration of the loop body is a separate scope. If you declare a new variable inside the loop body, it lives until the end of that iteration then falls out of scope. Next time around the loop, you declare a new, separate variable.
There is a slight grey area around the loop header -- exactly when does the scope start and end? Older C compilers used to disagree about this, but the rules were firmed up in C++ (and I assume in recent versions of C too) and now loop headers use the tightest scope they can.
So I would expect “defer” to run at the end of each iteration, exactly the same as the C++-style RAII case, and that is in fact how it works in every modern language except Go.
Another way to think about it that might be helpful: most languages try to implement defer in a completely static way, where just looking at the syntax, you can figure out exactly where and when defer handlers are going to run. You can allocate all the storage you need at the start of the function, and nothing tricky is required at runtime. If defer handlers are queued up and run as a batch later on, that’s dynamic behavior that needs some extra runtime support, and that’s why most languages don’t do it.
Thanks for the correction. You are right. I wasn’t thinking straight.
Go's defer function is very predictable, not so much because Go's defer is magic but precisely because it isn't. There aren't a ton of features for it to interact with.
This also isn't a special virtue of Go; IMHO it's a special (not unique, but unusual) failing of C++. It was insanely complicated already in the 90s and every standard since then has only made it worse.
I don't think it's super valuable to mimic the Go syntax anyway. In C++ you can fairly easily implement a class that works like this, which is fairly clean and intuitive:
void func() {
Deferred deferred;
for (int i = 0; i < 10; ++i) {
deferred.push([] { some callback });
}
// when Deferred is destroyed, it executes callbacks.
}
This is somewhat more powerful than Go's defer in that it gives the user explicit control over the scope. I think in Go you'd have to create a closure and call it immediately to create a nested scope for the purposes of executing deferred statements early.Granted, both BOOST_SCOPE_EXIT and your defer macro work at block end, not function end, but I agree with OP that this is practically always the better approach, so I don't think we should look at this thing as C++ trying to copy go features with lousy hacks. It's probably the other way around, if anything. And modern implentations in C++ (like yours) are extremely straightforward.
the downside is having to write nullptr instead of just the closure. Something like this really should be part of the standard.
I clicked on it with the assumption it was about loading resources in `script` tags with the defer or async attributes.
It was by then reading said article when I saw it was about Go and not JS loading. :)