4 ms·
In RAII languages[0], you obviously don't need unrestricted gotos. However, I always find myself missing it when writing nested loops. Labeled break and contin
by kmeisthax 4y ago
In RAII languages[0], you obviously don't need unrestricted gotos.
However, I always find myself missing it when writing nested loops. Labeled break and continue[1] ought to be considered standard structure programming primitives. These are restricted gotos and allowing them to break or continue a parent loop doesn't unrestrict them much. But it does significantly improve the expressive power of your looping constructs.
[0] C++, Rust, Go, or anything else with automatic memory management and destructors
[1] I've also heard of numbered break/continue. Personally I think this isn't good enough: what if I need to move loops around? That will change the meaning of a `break 2`. With a `break OUTER` the compiler will yell at me if I remove the outer loop without changing all the code that breaks out of it to target a different one.
- jolux 4y agoGo doesn't have RAII or destructors, it has defer.
- tedunangst 4y agoWhich definitely does weird things in a loop.
- Groxx 4y agoIIFEs trivially solve that. tbh I see FAR more range-var-misuse with Go loops than defers. I've seen over 100 range-var problems, and seen lints catch many more (several thousand), but I've only seen a loop defer issue once. When a defer is needed, it seems like people both remember the issue better (`defer` is a new construct for many, `for` is not and habits from other languages can mislead them), and the code is complex enough to justify a helper func, where a defer is trivially correct.
- nextaccountic 4y agodefer proposal for C made in 2021 https://www.open-std.org/jtc1/sc22/wg14/www/docs/n2895.htm https://www.open-std.org/jtc1/sc22/wg14/www/docs/n2895.htm ... why isn't this in C already?
- moregrist 4y agoThis is one of those half-true claims that C++ aficionados make, probably because they can play a bit fast and loose with resource cleanup in their particular situations. RAII simplifies some of the resource cleanup, but at a cost: if the resource cleanup fails, there’s essentially no way to convey this. So yes, you can write a destructor that tries to clean up your resources regardless of how the code exits its current scope. But if that cleanup encounters a problem, the destructor can, at best, try to convey this indirectly, either by (commonly) logging or (rarely) setting a variable to a value. It certainly cannot throw another exception, or even directly manipulate the return value of the function. This is fine for some purposes, and completely unacceptable for others. But it’s not equivalent to the explicit cleanup and error handling in C. So some of us occasionally find ourselves in an RAII language and using goto.
- vlovich123 4y agoYou can totally throw an exception from RAII code if you declare the destructor noexcept(false). Goto + RAII isn’t generally compatible unless you structure things very carefully
- moregrist 4y agoYou can, and your program will call std::terminate if there’s already an exception being processed. Not exactly desirable if you’re trying to write code that ensures careful resource cleanup. Also why it’s widely regarded as _wrong_ to ever throw in a destructor.
- kentonv 4y agoIMO this is a design bug in C++. The authors couldn't agree on what to do in the exception-during-unwind scenario, so they chose the worst possible option: crash. In most cases, an second exception raised while another exception is already being thrown is merely a side-effect of the first exception, and can probably safely be ignored. If the idea of throwing away a secondary exception makes you uncomfortable, then another possible solution might have been to allow secondary exceptions to be "attached" to the primary exception, like `std::exception::secondary()` could return an array of secondary exceptions that were caught. Obviously there's some API design thought needed here but it's not an unsolvable problem. If we could just change C++ to work this way, then throwing destructors would be no problem, it seems? So this seems like a C++-specific problem, not fundamental to RAII. That said, there is another camp which argues that it fundamentally doesn't make sense for teardown of resources to raise errors. I don't think you're in this camp, since you were arguing the opposite up-thread. I'm not in that camp either.
- quietbritishjim 4y ago> Labeled break and continue ... I find that normally if I need nested break then it suffices to refactor the target loop into a function and use return instead. I don't think I normally miss multilevel continue, but the same strategy would work for that too. You'd just pull out the target loop body rather than the whole loop. If that doesn't work because you need to select too many different break/continue levels (more than two) then maybe it's time to review the complexity of the function anyway.
- fsckboy 4y ago> You'd just pull out the target loop body rather than the whole loop. that's a "good solution" in the sense of "lambda is the ultimate goto", but if you're writing a loop over the rows and columns (or more dimensions) of something, pulling out and segregating the control structure for the innermost (and potentially other) layers of the hierarchy can make a simple depth first exploration look obscure. If the total amount of code is fitting in a screenful, I'd rather put multi-level break or continue labels and then goto them sparingly.
- quietbritishjim 4y agoThat sounds like a case where some sort of iterator class would be best (or, in C, an iterator "class" made of a struct with associated functions). It could have helper methods for .next_column() etc., and you'd just have one overall loop. That might make the code more complicated, overall, but that's the trade off of structured programming - occasionally there's more complexity but it's so exceptionally rare that it's still worth it overall. (Then again ... perhaps it would make the code arguably simpler anyway.)
- kentonv 4y ago> In RAII languages[0], you obviously don't need unrestricted gotos. You don't need them for teardown, but they still make sense for retries -- cases where a procedure needs to start over after hitting certain branches, e.g. a transaction conflict. I think `goto retry` is a lot more readable than wrapping the procedure in `do { ... } while (false)` and using `continue` to retry. `goto` works very nicely with RAII here in that it'll invoke the destructors of any local variables that weren't declared yet at the point being jumped back to.
- jandrewrogers 4y agoEven with RAII in C++, goto is still useful for handling the occasional error case where you need to reset/retry some operation e.g. due to transient hardware issues that are not unrecoverable errors. A common example that comes to mind immediately is asynchronous disk reads where the data was corrupted during transfer but may succeed if transparently cleaned up and re-issued. I use goto rarely. But there are times when anything else would be inelegant, and in those instances I'll use it without hesitation.
- rightbyte 4y agoGotos are quite nice for some state machines too. You essentially store the state in the instruction pointer instead of some helper variable.
- mort96 4y ago> [0] C++, Rust, Go, or anything else with automatic memory management and destructors Go's automatic memory management (GC) doesn't come in to play here, but the defer statement does. It doesn't make Go a RAII language, but it makes Go a language with a nicer "run this code at the end of the stack frame" feature than goto.