10 ms·
> goto (not even as a reserved word) I haven't done this for a while, but once upon a graduate program I wrote a compiler from a made-up-language (MUP) to C.
by IvyMike 11y ago
> goto (not even as a reserved word)
I haven't done this for a while, but once upon a graduate program I wrote a compiler from a made-up-language (MUP) to C. MUP had some strange control structures, and if C did not have "goto", it would have been a lot more difficult to implement those structures. Since then, I have always thought languages should have a "goto" statement that human-written code is not allowed to use. :)
- vezzy-fnord 11y agoAll control flow is ultimately derived from goto (or JMP).
- pcwalton 11y agoIt often simplifies a lot of the compiler implementation if you only have reducible control flow. LLVM is fine with irreducible control flow, because it has to handle C, but rustc uses a CFG for the borrow checker, which is flow-sensitive. You can convert irreducible control flow to reducible control flow, but it can explode the size of the graph in pathological cases. (I don't recall whether the borrow checker actually depends on the control flow being reducible, but some of the possible future improvements we've talked about with "non-lexical lifetimes" definitely do. There are also nasty interactions between RAII and irreducible control flow…)
- gsg 11y agoYeah. Even basic things like building SSA can be done more simply on "structured" CFGs. The benefits are enough that data structure and algorithm designs in the JVM compiler world often take advantage of assuming reducible control flow even though Java bytecode can express irreducible CFGs. Instead, such programs are left to the interpreter.
- cwzwarich 11y agoRust's CFGs are reducible with unbounded treewidth, not structured, because Rust has named exits from loops that nest arbitrarily. Most problems on reducible graphs are not easier than for general graphs, because you can always compute a loop forest (for generalized loops, not natural loops with a single entry point) and just consider a derived acyclic graph.
- gsg 11y agoThe structured SSA building algorithm I had in mind has a fairly simple extension that covers break, continue, and early return. It's more complicated, but still way simpler than iterated dominance frontiers. > you can always compute a loop forest It's easier to not have to.
- cwzwarich 11y agoTechnically speaking, break / continue / early return are unstructured by the classical definition. The algorithm you are likely thinking of can easily be extended to handle arbitrary control-flow: https://pp.info.uni-karlsruhe.de/uploads/publikationen/braun13cc.pdf https://pp.info.uni-karlsruhe.de/uploads/publikationen/braun... In the case of irreducible control-flow it may produce redundant cyclic phis, but those can be eliminated with a pretty simple post-pass.
- cwzwarich 11y agoNon-lexical lifetimes don't depend on reducibility. The RFC I wrote works fine for irreducible CFGs: https://github.com/rust-lang/rfcs/pull/396 https://github.com/rust-lang/rfcs/pull/396 It does have the effect of picking a single dominating entry point for a loop with multiple entry points, but if you want a single-entry region that's necessarily true. Multiple-entry regions are probably possible, but they could be counterintuitive and produce even stranger error messages than the current region system. The borrow checker is based on dataflow analyses that should work on arbitrary CFGs, assuming an appropriately generalized notion of region.
- azakai 11y ago> You can convert irreducible control flow to reducible control flow, but it can explode the size of the graph in pathological cases. True, but only in node-splitting approaches. If you use a label threading variable (as emscripten's relooper does), there is a guaranteed reasonable limit on code size increase (at the cost of performance).
- haberman 11y agoAt least reserve the word, in case you change your mind in the future? I guess it's too late now...
- Maken 11y agoBurn the boats and never look back, I guess.
- GFK_of_xmaspast 11y agoSince when does rust care about breaking changes to the language.
- steveklabnik 11y agoSeven weeks ago today.
- mcguire 11y agoFor version 1? Can we break on 2.0?
- steveklabnik 11y agoIt's a bit more subtle than that, see https://github.com/rust-lang/rfcs/blob/master/text/1122-language-semver.md https://github.com/rust-lang/rfcs/blob/master/text/1122-lang...
- EugeneOZ 11y agoI think the point of parent comment is "it's ok to don't have goto in Rust, but it's not the reason to be proud of just not having goto". Goto is important enough operator and doesn't deserve blind hate.
- tracker1 11y agoAgreed... I honestly tend to break code into a lot of discrete functions, and combine for workflows, but sometimes you just need to easily jump back a few places, and where goto is available it's not always a bad option. Just one that should be used sparingly... once in a complex workflow is fine.. more than twenty times in a few thousand line method, not so much.
- mcguire 11y agoDon Knuth, "Structured Programming with go to Statements": "Just recently, however, Hoare has shown that there is, in fact, a rather simple way to give an axiomatic definition of go to statements; indeed, he wishes quite frankly that it hadn't been quite so simple.... "Informally, α(L) represents the desired state of affairs at label L; this definition says essentially that a program is correct if α(L) holds at L and before all "go to L" statements, and that control never "falls through" a go to statement to the following text. Stating the assertions α(L) is analogous to formulating loop invariants. Thus, it is not difficult to deal formally with tortuous program structure if it turns out to be necessary; all we need to know is the "meaning" of each label."
- CyberDildonics 11y agoI've used goto in very specific cases which basically boils down to breaking from inner loops to the outside of the outer loop. Thankfully Rust actually has labeled break and continue statements, so this case of using goto is taken care of.
- sunfish 11y agoI wish C/C++ had a labeled break construct, like JavaScript, Java, Rust, and other languages have. It's surprisingly powerful, while still remaining structured. I personally have enjoyed learning about it, and about just how rarely an actual goto is really needed.
- api 11y agoI've never had to use 'goto' in C++ except to break from a nested loop. In C++ labeled breaks would make 'goto' completely obsolete. In C it would still have use in the implementation of orderly error handling -- the pattern where you hand-implement exception handling in C by putting an on_error: label at the end of the function that is goto'd on error. The addition of some orderly construct for this in C would eliminate that case, leaving no real role for goto there either.
- jasode 11y ago>In C++ labeled breaks would make 'goto' completely obsolete. In 20 years of using C/C++, I've found one use for "goto" that's hard to substitute: simulating coroutines that yield to an outer context. (Similar to C# yield return). A "break label" gets you out of a loop. I needed to use "goto" to jump back into the middle of a loop to resume where the "coroutine" previously left off. The keywords "break/setjmp/longjmp" wouldn't have been substitutes for this particular use case.
- prutschman 11y agoI've used a switch statement essentially as a jump table for this purpose. I'm curious whether that would have worked, or if you were doing something different enough that you needed a goto.
- IsTom 11y agoI don't quite understand how it's not a setjmp/longjmp usecase. Isn't this exactly what longjmp is for?
- frivoal 11y ago
- justincormack 11y agoLua introduced goto in a recent release, to some surprise, but it is useful especially for code geneation and interpreters.
- Animats 11y agoI don't think I've written a goto since the 1970s, and I've written a lot of code since then. If you need to bail out of something in the middle, make it a function.
- bluecalm 11y agoAs mentioned above, in C goto is often useful for error handling (where you need to free some resources before exiting the function). It's way more convenient and readable to write freeing code once and jump to it instead of having multiple return exits and duplicate the code before every single one of them.
- derf_ 11y agoWhat the parent said still applies in your case. You just make a wrapper function that does the cleanup. I have used this pattern many times: static int foo_impl(int arg, int **resource1, int **resource2) { *resource1 = (int *)malloc(sizeof(**resource1)); if (*resource1 == NULL) return EXIT_FAILURE; /* Do something with resource1 (omitted)... */ *resource2 = (int *)malloc(sizeof(**resource2)); if (*resource2 == NULL) return EXIT_FAILURE; /* Do something with resource1 and resource2 (e.g., stick them in a global hash table as key/value pairs, or whatever, omitted)... */ return EXIT_SUCCESS; } int foo(int arg) { int *resource1; int *resource2; int ret; resource1 = resource2 = NULL; ret = foo_impl(arg, &resource1, &resource2); if (ret != EXIT_SUCCESS) { free(resource2); free(resource1); } return ret; } The advantages are that it makes it very explicit which resources need to be cleaned up (making it easier to review to be sure you haven't forgotten one, or forgotten to initialize one, because they're in a nice list), and it's harder to screw up the control flow. You can't get out of the function without passing by the clean-up code, and you can't accidentally fall into the clean-up code without explicitly returning an error.
- luso_brazilian 11y agoI know it is just an example to illustrate the point but this seems to have a bug in it: resource2 won't be initialized if the function fail to allocate resource1 so resource2 will be null when the code enters the deallocation condition. A way to fix it would be to return different failures for every allocation and to use a switch without a break to clean up, something like: switch (ret) { case EXIT_SUCCESS: free(resource2); case EXIT_FAILURE2: free(resource1); case EXIT_FAILURE1: break; } EDIT: added potential fix to the code. Sorry if there is any bug in the fix, my C is a little bit ... rusty. EDIT2: fixed a bug in the fix. C is hard, let's go shopping. EDIT3: the explanation for the bug was also wrong. It is not a memory leak for but freeing a null pointer. Fixed too.
- kazinator 11y ago> Since then, I have always thought languages should have a "goto" statement that human-written code is not allowed to use. It's an interesting idea. The problem I see is that users can get around it by wrapping goto in the simplest possible macro. Then the idea has backfired: we now have goto under as many names as there are goto-using programmers. :)
- chjj 11y agoI've always defended goto and gotten a lot of flak for it. As soon as I say, "I wish I had X-language had gotos", I see jaws drop. Response: "Wow, haven't you heard the news?! GOTOs are considered harmful!" I think goto should be in almost every language. It's one of the most primitive instructions, why shouldn't it be available when needed? Yes, it can be misused, just like any other feature in the language, but it can also be used to great benefit. State machines, for example, can make good use of gotos. Hell, look at any unix library your machine uses every day. The ones you don't even think about. Start with libncurses: I promise there's a few gotos where needed. $ find ~/ncurses-5.9 -name '*.c' -print0 | xargs -0 grep goto | wc -l 70 Raise your hand if you plan to stop using ncurses because of how opposed to how "harmful" goto statements are. Oh, here's tmux, if you're interested (one of the most beautifully written C programs): https://github.com/tmux/tmux/search?utf8=%E2%9C%93&q=goto https://github.com/tmux/tmux/search?utf8=%E2%9C%93&q=goto
- jxf 11y ago> It's one of the most primitive instructions, why shouldn't it be available when needed? I see what you're saying, though I don't find this a compelling argument. By this logic, why not allow direct register access in all languages? Just because something is a primitive operation doesn't mean you want to include it, especially not if it's more difficult to enforce guarantees your language would like to make about valid programs.
- MaulingMonkey 11y ago> Raise your hand if you plan to stop using ncurses because of how opposed to how "harmful" goto statements are. What if I just avoid contributing to the ncurses codebase? I've used plenty of useful tools with absolutely horrific codebases that I'd never want to touch in a million years. Not sure if ncurses is one of them. The whole "it gets used, ergo it must be a good idea" argument doesn't hold much traction with me - even if I think using it when in C, to enforce single exit style, to work around the lack of RAII constructs, goto is the lesser evil. I started with GOTO in BASIC. I used it a lot. It structured my initial reasoning about control flow. Despite this, in the past few years, I've used a naked goto maybe once or twice, and in all cases later rewrote it without the goto, which in my opinion increased it's readability. (I generally always have the option of C++ over C, and choose it, rendering single exit style 'useless'.) > Oh, here's tmux, if you're interested (one of the most beautifully written C programs): https://github.com/tmux/tmux/search?utf8=%E2%9C%93&q=goto https://github.com/tmux/tmux/search?utf8=%E2%9C%93&q=goto Most of those are single exit style gotos. Those that aren't, do cause some concern, despite being "one of the most beautifully written C programs", even to their original author from the looks of it: if (errno == ENOMEM) goto retry; /* possible infinite loop? */ For what it's worth, it seems unlikely to be an infinite loop, short of encountering a bug in sysctl, or another process/thread constantly adding data. I had to google the header path to find an appropriate manpage (i.e. not _sysctl, not sysctl the program) to figure this out... I've encountered worse edge cases before, however, and I'd really prefer my programs crash properly, instead of hanging when they do. > State machines, for example, can make good use of gotos. The performance complaints about an additional branch misprediction when using the "for(;;) switch(...)" style without gotos, is one of the few arguments that moves me, if only slightly. That seems like a case your standard optimizing compiler really should be able to handle, however. I'll assume they don't, as I'm too lazy to test if this is merely hearsay... That said, I'll even use "goto case" in C# on occasion where I'd use case fallthrough in C++, if I'm feeling particularly lazy and don't want to turn things into proper method calls that can simply call each other just yet. I usually clean it up before I start to confuse myself. It's not something I'd miss if it were gone, however. It's something I use only rarely, and only as a crutch to stave off cleanup. Not exactly a ringing endorsement. EDIT: Code formatting, proper insertion of subject...
- pjmlp 11y agoI only used goto in ZX Spectrum Basic, GW Basic and several Assembly flavours. All its use cases are better served by other language constructs. Then again, C is a portable macro assembler.