4 ms·
Hmm that's certainly an interesting project. But I have to wonder if writing "defer file.close()" everywhere is really superior to RAII. Isn't defer also kind o
by pingyong 7y ago
Hmm that's certainly an interesting project. But I have to wonder if writing "defer file.close()" everywhere is really superior to RAII. Isn't defer also kind of hidden control flow? At that point I feel like RAII simply offers more, first of all you don't have to write what you want to defer everywhere, and second of all you can actually move resources around and still rely on it.
Also the integer overflow section struck me as kinda odd - what if I want wrapping behavior? That's not exactly a super uncommon scenario.
- clarry 7y agoWrapping is not a good default, and actually it's not that common a scenario. When you want arithmetic to wrap, you should use some way to explicitly denote that you want it to wrap. (I think it could look like an operation with a modulo reduction or mask applied to it.) defer is way more transparent than RAII, because your code is right there in the function call, not hidden in a pile of classes that might change underneath.
- pingyong 7y agoI don't mind it not being default - but the section doesn't mention at all how to get wrapping behavior. Just one sentence like "use this different operator to get wrapping behavior" would be fine, but since they didn't mention it at all I kind of went away with the idea that it is not supported at all, which honestly would be a massive problem. >defer is way more transparent than RAII, because your code is right there in the function call, not hidden in a pile of classes that might change underneath. I mean, sort of? It seems way less composable though, if you have an object which contains 5 different resources, now you have to defer 5 things? How does this interact with return values, if you defer file.close() for example but return the file, does it still get closed? And if you have a heap allocated array of those objects, where objects get inserted at different places, defer doesn't work at all anymore, since it's just bound to function scope? Because that's usually the scenario that can be annoying to handle in C (opposed to just closing whatever was opened in the same function scope), so it seems a bit odd to me to kind of violate that "no hidden control flow" principle for something that doesn't even solve the primary issue that C resource handling has.
- clarry 7y ago> I don't mind it not being default - but the section doesn't mention at all how to get wrapping behavior. I think that piece would get too long if it described everything in depth. https://ziglang.org/documentation/master/#Wrapping-Operations https://ziglang.org/documentation/master/#Wrapping-Operation... > I mean, sort of? It seems way less composable though, if you have an object which contains 5 different resources, now you have to defer 5 things? Defer is used for scope-local resources. If you create five different things in the local scope that need to be cleaned up, then they are most likely not part of the same object; yes, you will need to clean them all up. If you have an object with five different thing, then you would defer that object's cleanup routine. > How does this interact with return values, if you defer file.close() for example but return the file, does it still get closed? Yes. Why would you defer close on a file that you are planning to return? That's by definition not some scope-local resource with scope lifetime you want to cleanup at the end of scope. That's like implementing malloc that frees the block before returning. Note that there is errdefer, which you can use to defer the release of resources that would be returned normally but need to be cleaned up on error. https://ziglang.org/documentation/master/#defer https://ziglang.org/documentation/master/#defer > And if you have a heap allocated array of those objects, where objects get inserted at different places, defer doesn't work at all anymore, since it's just bound to function scope? I don't understand what you're asking about here. If you malloc() an array in C, then you free() it when you're done with it. The exact same mechanism works in Zig, but defer also helps you ensure free() actually gets called when you go out of scope, so you can still use early returns instead of ret = foo; goto err_bar; > Because that's usually the scenario that can be annoying to handle in C (opposed to just closing whatever was opened in the same function scope), so it seems a bit odd to me to kind of violate that "no hidden control flow" principle for something that doesn't even solve the primary issue that C resource handling has. I don't agree that it's violating the "no hidden control flow constraint." It's explicit, right there in the scope, not stashed away in a bunch of destructors. It's as transparent as return or goto or break. Again, I don't really understand the rest of your complaints (or what's your primary issue with C's resource handling). Defer is just a mechanism to make it easier to release resources whose lifetime is tied to a scope.
- 7y ago
- ptrwis 7y agoFor wrapping behaviour you need to add % symbol, like a+%b is wrapping addition.
- adrift 7y agoRAII requires even more boilerplate.
- pingyong 7y agoI'm not sure how RAII could ever need more code, when the only difference is that functions get called automatically (instead of through close(), or a close() inside a defer).
- klodolph 7y agoThere’s way more going on with RAII. With RAII, you’re expressing ownership in code. That means that you have to write all the extra code to track that ownership in the type system or at runtime. Every time I want to write a class that manages a resource with RAII, I am almost always writing a copy constructor, move constructor, assignment operator, move assignment operator, and destructor. On the other hand, if I write the equivalent code in Go or Nim, I can just add something like a Close() method. Because ownership isn’t expressed in code, there is a higher chance to make mistakes, but the code is simpler.
- edflsafoiewq 7y ago> Every time I want to write a class that manages a resource with RAII, I am almost always writing a copy constructor, move constructor, assignment operator, move assignment operator, and destructor. No you aren't. You only do that at the lowest abstraction level. For normal classes you can add a vector member and it will just work (see "rule of zero"). > On the other hand, if I write the equivalent code in Go or Nim, I can just add something like a Close() method. Yes, and then find and update every place in the code where that class is instantiated. If the class was ever instantiated as a temporary, you also need to pull it out into a named variable so you can use defer. If the class was ever used in a vector, you need to find all the places where an element of the vector is removed and make sure close is being called there, etc. This is really awful, so in a language without RAII, you are strongly incentivized not to tie ownership to object lifetime. I've found the best way is to aggressively simplify managed lifecycles and do allocation/cleanup in some kind of manager object.