5 ms·
Does anyone know if RAII types will ever be a thing in Zig?
by levzettelin 5y ago
Does anyone know if RAII types will ever be a thing in Zig?
- asddubs 5y agomaybe since Zig still is making breaking changes, they can call it IIRA
- kristoff_it 5y ago`defer` and `errdefer` give you 90% of what destructors & co give you for 0% of the complexity. For now the the closest thing to RAII is this proposal, but there is no guarantee that it will be accepted. https://github.com/ziglang/zig/issues/782 https://github.com/ziglang/zig/issues/782
- initplus 5y agoI find in C++ at least reasoning about RAII is always surprisingly complex. The second you have to write a custom destructor you get into the weeds of reasoning about copy/move/copy-assignment etc. https://en.cppreference.com/w/cpp/language/rule_of_three https://en.cppreference.com/w/cpp/language/rule_of_three C++ RAII also rubs up painfully against handle based APIs (looking at you Windows) in my experience. There is a lack of standardized RAII wrappers for handle types like there is for pointers. Yes you can pull in a custom handle RAII wrapper but at that point it's simpler to just manage manually. Defer on the other hand is simple. Anyone can understand it in five minutes.
- jcelerier 5y ago> There is a lack of standardized RAII wrappers for handle types like there is for pointers. .. what's wrong with #include <memory> template<typename T, auto Free> using safe_handle_t = std::unique_ptr<T, decltype([] (auto p) { Free(p); })>; using file_handle = safe_handle_t<FILE, fclose>; void file_example() { file_handle f{fopen("foo", "r")}; }
- 10000truths 5y agoNow you have a double indirection. Worse, the handle probably already references dynamically allocated memory. So that’s two dynamic memory allocations per “safe” handle. Which might be perfectly acceptable for non-critical programs, but it is certainly going to tank your memory usage efficiency and thrash your CPU cache if scaled up to millions of handles.
- fbkr 5y agoThere is no double indirection and allocation here, it stores the FILE* directly, not as a pointer to a FILE*.
- jcelerier 5y agoThis is entirely wrong. This produces exactly the same code than auto f = fopen("foo", "r"); if(f) fclose(f); check for yourself: https://gcc.godbolt.org/z/YxaPqYGez https://gcc.godbolt.org/z/YxaPqYGez
- spacechild1 5y agoThat's what I do as well. Many people are not aware that std::unique_ptr can do more than just manage heap memory.
- dnautics 5y agoI'm pinning my hopes that certain types can be marked as "shared resources", either in-lang, or through a minimalistic helper tool - and analyzed at a lower level (ZIR or AIR) for lifetime analysis.
- AndyKelley 5y agoWhen people talk about RAII in relation to Zig I think they mean something slightly different than RAII, but then the conversation starts to become about what is the definition of RAII rather than whether the Zig language is lacking a certain kind of useful abstraction. Examples: [1]: https://news.ycombinator.com/item?id=29506814 https://news.ycombinator.com/item?id=29506814 [2]: https://gist.github.com/andrewrk/190170bc1441839644c3f15725a22096 https://gist.github.com/andrewrk/190170bc1441839644c3f15725a...
- nikki93 5y agoNot saying this means you actually need to add constructors and destructors, but I think the main tricky bit is when you have eg. a grow / shrinkable array and want to call something on it that removes elements and it should be calling destructors on those. AFAICT there's no clear place to add a `defer` that makes it happen at the right time at the lexical site those elements are originally added. I think this is where the main RAII complexity comes from vs. the lexical scope local variable scenario which is definitely handled by `defer`. All that said, not an argument for having that, just wanted to nuance it. I think not having it and keeping the language focused on its priorities can be good, for example.
- tialaramex 5y agoSurely the only part you need for this scenario is a way to dispose of whatever resources are behind the elements? So if you have interfaces a Disposable interface is enough, the code can determine that these elements are Disposable and dispose of them when they're removed. That's quite a lot less intense than needing a language-level concept of destructors. If you're writing a low-level language it might well be better to hook this in explicitly like Rust's Drop trait (the Drop trait is a langitem in Rust, it must exist), but in high-level languages I think you'd get almost all the value from a Disposable interface even if there is no actual language support provided.
- Spex_guy 5y agoHaha yeah this is a good insight. "Resource Acquisition Is Initialization" actually has nothing to do with resource acquisition or initialization in most peoples' minds, instead people (including me) tend to think of it as "Resource Release Is Implicit Object Lifetime Termination". RRIIOLT doesn't really have the same ring to it though xD
- Spex_guy 5y agoIt's unlikely. RAII comes with a surprising amount of complexity. In order to have a reasonably complete language that has RAII and value types, you must also have: - constructors - destructors - overloadable copy assignment operators - placement new - move semantics and rvalue references These features come together or not at all. If you lose any of them, the language becomes less complete. I think Rust and C++ are doing a fine job of exploring the design space of languages that have this feature set, but it's too much complexity for Zig. Edit: As commenters have pointed out, this exact manifestation of these features is not required. A more correct set of requirements is: - a notion of beginning and ending object lifetimes in existing memory - a notion of move semantics to relocate an existing RAII object - a notion of non-copyability for certain types, or an ability to override copy behavior
- steveklabnik 5y ago> In order to have a reasonably complete language that has RAII and value types, you must also have: - constructors - destructors - overloadable copy assignment operators - placement new - move semantics and rvalue references Rust has RAII and value types, and does not have constructors, overloadable copy assignment operators, placement new, or rvalue references (though we do of course have a very similar notion to rvalue/lvalue in general, but that's not the same thing as "rvalue references" with relation to all of this). While it has move semantics, they're significantly different.
- Spex_guy 5y agoI don't mean that these things need to manifest in exactly the same way as they do in C++, but analagous features are needed. You're right that rvalue references are not necessary, but some form of move semantics are. When I say constructors and destructors, I am really referring to having a concept of object lifetimes as part of the language. Zig does not have this, and is much simpler because of it. Edit: to clarify, the thing that makes a constructor/destructor useful in this case is the property that it begins/ends an object lifetime according to the language. This lifetime reasoning certainly has benefits, like the ability to have const fields in C++ and the ability to do static checking of lifetimes in Rust. However it also comes with significant complexity, because move semantics are needed throughout the language, and begin/end lifetime tags are needed when implementing data structures that use preallocated backing arrays.