4 ms·
The article is a bit dense, but what it's announcing is effectively golang's `defer` (with extra braces) or a limited form of C++'s RAII (with much less boilerp
by kjgkjhfkjf 8mo ago
The article is a bit dense, but what it's announcing is effectively golang's `defer` (with extra braces) or a limited form of C++'s RAII (with much less boilerplate).
Both RAII and `defer` have proven to be highly useful in real-world code. This seems like a good addition to the C language that I hope makes it into the standard.
- Zambyte 8mo agoProbably closer to defer in Zig than in Go, I would imagine. Defer in Go executes when the function deferred within returns; defer in Zig executes when the scope deferred within exits.
- rwmj 8mo agoThis is the crucial difference. Scope-based is much better. By the way, GCC and Clang have attribute((cleanup)) (which is the same, scope-based clean-up) and have done for over a decade, and this is widely used in open source projects now.
- bashkiddie 8mo agoI would like to second this. In Golang if you iterate over a thousand files and defer File.close() your OS will run out of file descriptors
- Joker_vD 8mo agoWell, unless you're on Windows :D Even on Windows XP Home Edition I could open a million file handles with no problems. Seriously, why is default ulimit on file descriptors on Linux measly 1024?
- nasretdinov 8mo agoSome system calls like select() will not work if there are more than 1024 FDs open (https://man7.org/linux/man-pages/man2/select.2.html https://man7.org/linux/man-pages/man2/select.2.html), so it probably (?) makes sense to default to it. Although I don't really think that in 2k26 it makes sense to have such a low limit on desktops, that is true.
- remexre 8mo agohttps://0pointer.net/blog/file-descriptor-limits.html https://0pointer.net/blog/file-descriptor-limits.html
- CodesInChaos 8mo agoI wonder what the thought process of the Go designers was when coming up with that approach. Function scope is rarely what a user needs, has major pitfalls, and is more complex to implement in the compiler (need to append to an unbounded list).
- mort96 8mo agoI hate that you can't call defer in a loop. I hate even more that you can call defer in a loop, and it will appear to work, as long as the loop has relatively few iterations, and is just silently massively wasteful.
- usrnm 8mo agoThe go way of dealing with it is wrapping the block with your defers in a lambda. Looks weird at first, but you can get used to it.
- mort96 8mo agoI know. Or in some cases, you can put the loop body in a dedicated function. There are workarounds. It's just bad that the wrong way a) is the most obvious way, and b) is silently wrong in such a way that it appears to work during testing, often becoming a problem only when confronted with real-world data, and often surfacing only as being a hard-to-debug performance or resource usage issue.
- deleted 8mo ago[deleted]
- 9rx 8mo agoWhat's the use-case for block-level defer? In a tight loop you'd want your cleanup to happen after the fact. And in, say, an IO loop, you're going to want concurrency anyway, which necessarily introduces new function scope.
- 8mo ago
- jibal 8mo agodefer was invented by Andrei Alexandrescu who spelled it scope(exit)/scope(failure) [Zig's errdefer]/scope(success) ... it first appeared in D 2.0 after Andrei convinced Walter Bright to add it.
- L-4 8mo agoBoth defer and RAII have proven to be useful, but RAII has also proven to be quite harmful in cases, in the limit introducing a lot of hidden control flow. I think that defer is actually limited in ways that are good - I don't see it introducing surprising control flow in the same way.
- fauigerzigerk 8mo agoBut of course what you call "surprising" and "hidden" is also RAII's strength. It allows library authors to take responsibility for cleaning up resources in exactly one place rather than forcing library users to insert a defer call in every single place the library is used.
- gpderetta 8mo agoRAII also composes.
- kibwen 8mo agoDefer is also hidden control flow. At the end of every block, you need to read backwards in the entire block to see if a defer was declared in order to determine where control will jump to. Please stop pretending that defer isn't hidden control flow. > RAII has also proven to be quite harmful in cases The downsides of defer are much worse than the "downsides" of RAII. Defer is manual and error-prone, something that you have to remember to do every single time.
- sparkie 8mo agoDefer is a restricted form of COMEFROM with automatic labels. You COMEFROM the end of the next `defer` block in the same scope, or from the end of the function (before `return`) if there is no more `defer`. The order of execution of defer-blocks is backwards (bottom-to-top) rather than the typical top-to-bottom. puts("foo"); defer { puts("bar"); } puts("baz"); defer { puts("qux"); } puts("corge"); return; Will evaluate: puts("foo"); puts("baz"); puts("corge"); puts("qux"); puts("bar"); return;
- throwaway27448 8mo agoThis certainly isn't RAII—the term is quite literal, Resource Acquisition Is Initialization, rather than calling code as the scope exits. This is the latter of course, not the former.
- mort96 8mo agoPeople often say that "RAII" is kind of a misnomer; the real power of RAII is deterministic destruction. And I agree with this sentiment; resource acquisition is the boring part of RAII, deterministic destruction is where the utility comes from. In that sense, there's a clear analogy between RAII and defer. But yeah, RAII can only provide deterministic destruction because resource acquisition is initialization. As long as resource acquisition is decoupled from initialization, you need to manually track whether a variable has been initialized or not, and make sure to only call a destruction function (be that by putting free() before a return or through 'defer my_type_destroy(my_var)') in the paths where you know that your variable is initialized. So "A limited form of RAII" is probably the wrong way to think about it.
- throwaway27448 8mo ago> and make sure to...call a destruction function Which removes half the value of RAII as I see it—needing when and to know how to unacquire the resource is half the battle, a burden that using RAII removes. Of course, calling code as the scope exits is still useful. It just seems silly to call it any form of RAII.
- usrnm 8mo agoIn my opinion, it's the initialization part of RAII which is really powerful and still missing from most other languages. When implemented properly, RAII completely eliminates a whole class of bugs related to uninitialized or partially initialized objects: if all initialization happens during construction, then you either have a fully initialized correct object, or you exit via an exception, no third state. Additionaly, tying resources to constructors makes the correct order of freeing these resources automatic. If you consume all your dependencies during construction, then destructors just walk the dependency graph in the correct order without you even thinking about it. Agreed, that writing your code like this requires some getting used to and isn't even always possible, but it's still a very powerful idea that goes beyond simple automatic destruction
- usrnm 8mo agoTo be fair, RAII is so much more than just automatic cleanup. It's a shame how misunderstood this idea has become over the years
- randusername 8mo agoCan you share some sources that give a more complete overview of it? I got out my 4e Stroustrup book and checked the index, RAII only comes up when discussing resource management. Interestingly, the verbatim introduction to RAII given is: > ... RAII allows us to eliminate "naked new operations," that is, to avoid allocations in general code and keep them buried inside the implementation of well-behaved abstractions. Similarly "naked delete" operations should be avoided. Avoiding naked new and naked delete makes code far less error-prone and far easier to keep free of resource leaks From the embedded standpoint, and after working with Zig a bit, I'm not convinced about that last line. Hiding heap allocations seems like it make it harder to avoid resource leaks!
- xerokimo 8mo ago> Hiding heap allocations seems like it make it harder to avoid resource leaks! Because types come in constructor / destructor pairs. When creating variables, you're forced to invoke a constructor, and when an the object's lifetime ends, the compiler will insert a destructor call for you. If you allocate on construction and de-allocate on destruction, it'll be very hard for the leak to happen because you can't forget to call the destructor
- omoikane 8mo ago> with extra braces The extra braces appear to be optional according to the examples in https://www.open-std.org/JTC1/SC22/WG14/www/docs/n3734.pdf https://www.open-std.org/JTC1/SC22/WG14/www/docs/n3734.pdf (see pages 13-14)