1. 49
    1. 54

      It is a modern myth (featured here and, as another example, in Smith’s “Notes on structured concurrency”) that Dijkstra’s concern about GOTO had anything to do with jumping from one function to another. As far as I can tell, this is something current programmers have made up because it sounds right - it sounds like what is scary about GOTO is how it can jump “anywhere.” This is just completely made up. As far as I know Smith popularized this misconception in pursuit of a rhetorical argument about the unrelated “go” operator; if anyone has earlier citations explaining GOTO in terms of “jumping from one function to another” I’d be interested in them.

      Dijkstra was actually making a specific argument about the grounding for formal analysis of programming. He expresses it poorly because the language for the statements he was trying to make in Notes on Structured Programming was still being invented. Incidentally, his argument was not really correct, as Hoare later demonstrated (I can’t find the citation just now, but Knuth alludes to this in “Structured programming with goto statements.”) Axiomatic semantics can accommodate GOTO just fine: you need to describe the necessary preconditions of each label and then validate that they hold at every GOTO pointing to that label. This isn’t so different from formally proving code with a function call, because of course function calls are also jumps (and they even jump to other functions! spooky!).

      On the other hand, the argument against GOTO popularized as structured programming was that blocks should have a single entrance and a single exit, which has nothing to do with jumping between functions or with formal methods. This is attributed to Dijkstra, who mentions it as a rule of thumb, but it was really promoted by Harlan Mills at IBM, who pushed the ridiculous idea that the Böhm-Jacopini theorem was proof that this was a better way to program. This form of structured programming is also of course not enforced, as almost every language has either GOTO or some form of multiple exit (early return, break/continue, exceptions).

      The real reason we got rid of GOTO is that single entrance makes it a lot easier to understand what variables are in scope at any given time, which is a big part of informally reasoning about what code does. Multiple entrance lets you skip initialization; the only form of multiple entrance that’s been introduced to replace GOTO is coroutines, which avoid this issue. This is also why despite misunderstanding the problem with GOTO, this post is actually somewhat right: Go’s GOTO is “not so bad,” because it has a checker to guarantee that values are always initialized. Of course, initialization is only half the battle if you have mutable state, which is why I think any modern language should enforce that aliased data is immutable.

      I’ve been trying to finish a post explaining all of this history and more for the past 9 months.

      1. 4

        Interesting. I’m looking forward to reading that post.

        1. 3

          Make all aliased data immutable? You mean like Rust and unlike every other popular imperative language? I like Rust, but I think it’s a cruel and unusual punishment to work under its constraints unless you need the performance and footprint advantages it provides.

          1. 13

            Fitting. To me your comment is reminiscent of the people in the 60s who would’ve felt equally clever calling programming without GOTO “cruel and unusual punishment.” Some of us, however, are interested in finding tools and practices that reduce the probability of error in the code we produce.

            The problem of aliased mutable state was recognized even during the “structured programming movement,” though they didn’t know how to solve it. Here’s Tony Hoare in “Hints on programming language design” (1973), who makes a direct analogy between aliased mutable state and and GOTO:

            In a high-level language, the programmer is deprived of the dangerous power to update his own program while it is running. Even more valuable, he has the power to split his machine into a number of separate variables, arrays, files, etc.; when he wishes to update any of these he must quote its name explicitly on the left of the assignment, so that the identity of the part of the machine subject to change is immediately apparent; and, finally, a high-level language can guarantee that all variables are disjoint, and that updating any one of them cannot possibly have any effect on any other.

            Unfortunately, many of these advantages are not maintained in the design of procedures and parameters in ALGOL 60 and other languages. But instead of mending these minor faults, many language designers have preferred to extend them throughout the whole language by introducing the concept of reference, pointer, or indirect address into the language as an assignable item of data. This immediately gives rise in a high-level language to one of the most notorious confusions of machine code, namely that between an address and its contents. Some languages attempt to solve this by even more confusing automatic coercion rules. Worst still, an indirect assignment through a pointer, just as in machine code, can update any store location whatsoever, and the damage is no longer confined to the variable explicitly named as the target of assignment…

            …References are like jumps, leading wildly from one part of a data structure to another. Their introduction into high-level languages has been a step backward from which we may never recover.

            1. 1

              Pointers indeed were considered harmful for a long time. However, unlike goto, they’re still everywhere. I predict that this will remain true during the next 50 years. (I hope that C++ will mostly die during this time, so the only kind of pointer to remain will be a memory safe one; but most languages will achieve safety using dynamic mechanisms rather than static analysis the way Rust does.)

              1. 3

                The other well-understood way to achieve real logical safety around references (and not just memory safety) is with copy-on-write “value semantics,” like Swift does. I do think this is less preferable than the default behavior being a static error as in Rust, but I suspect that many languages of the next half century will adopt this solution to the problem because it is a lot easier to implement.

            2. 6

              I think it’s a cruel and unusual punishment to work under its constraints unless you need the performance and footprint advantages it provides.

              Personal opinion, but I feel a lot more confident writing Rust with its shared immutability guarantees than any other language. I feel like my ability to reason about the program grows drastically when I can be sure that an immutable reference cannot be mutated while I’m not looking. Not to mention the multithreading advantages this provides, as it completely prevents data races - and I like being sure that my software can scale to many threads if I ever deem that necessary.

              1. 3

                …and I don’t find Rust particularly more difficult than Python. The main situations where I find myself encountering friction are things like:

                1. Until this January, I was compiling Rust on an AMD Athlon II X2 270 from 2011.
                2. Little things related more to inexperience with explicitly typed languages, such as figuring out that I just needed to explicitly typecast my functions to make the type checker happy when I was populating a map from file extensions to ("human-readable description", handler_function) tuples.

                Of course, that could just be because I’d already developed a very similar coding style in Python to make testing and maintenance easier.

                1. 1

                  Same here. I’ve noticed that in Rust I don’t do defensive programming any more. If my function takes an array or some object, I don’t need to copy it just in case the caller modified it later.

                2. 3

                  unless you need the performance and footprint advantages it provides.

                  mutability xor aliasing has nothing to do with performance or footprint, but is all about correctness. Rust really really needs it, to keep the memory safety stuff correct, but even in high level languages like Python or OCaml, shared mutability can be a big source of bugs.

                  1. 2

                    The Rust borrow checker isn’t the only way to implement this policy. Pure functional languages meet this criterion. Plenty of unpopular imperative languages have travelled this road over the years as well. I’m implementing this policy in my language using what others have called “mutable value semantics”. The big idea is that the imperative parts of the language use copy semantics instead of reference semantics (aka aliasing). You can pass variables “by reference” and mutate referenced parameters in a function body, but aliasing is avoided using the “pass by copy-restore” parameter mechanism instead of pass by reference. You can add a borrow checker to the compiler as an optimization to avoid extra copies, but if the borrow checker fails due to aliasing, then copying occurs. So you don’t fight the borrow checker, it’s just an optimization. My language trades off performance for ease of use: avoiding cruel and unusual punishment is a design goal.

                  2. 3

                    While I agree with everything you said, I’ve always read the “Go To Statement Considered Harmful” letter as more grounded in Dijkstras experience with programming, and that his efforts in formalization are of the same lineage of thought, but neither is a pure consequence of the other.

                    Of course, that is just my interpretation of his texts and might be wrong.

                    1. 5

                      It’s true Notes on Structured Programming (“Considered Harmful” is an extract thereof) is a prelude to his work on formal methods, but he certainly thought the latter was the refinement of what he was attempting to do with the former. From his own notes shortly before his death:

                      Looking back I cannot fail to observe my fear of formal mathematics at the time. In 1970 I had spent more than a decade hoping and then arguing that programming would and should become a mathematical activity; I had (re)arranged the programming task so as to make it better amenable to mathematical treatment, but carefully avoided creating the required mathematics myself. I had to wait for Bob Floyd, who laid the foundation, for Jim King, who showed me the first example that convinced me, and for Tony Hoare, who showed how semantics could be defined in terms of the axioms needed for the proofs of properties of programs, and even then I did not see the significance of their work immediately. I was really slow.

                      https://www.cs.utexas.edu/users/EWD/transcriptions/EWD13xx/EWD1308.html

                      Your interpretation of it though is in line with Knuth’s, who’s “Structured programming with go to statements” follows the interpretation of “structured programming” which is more about subjectively structuring code to make informally understanding it more tractable. Since programmers don’t generally use formal methods, it’s Knuth’s version of structured programming and not Dijkstra’s that has prevailed.

                      1. 2

                        I would make the counter-point that we quite often rewrite our own history to make it more streamlined, and our later ideas will have more of an impact in that story. Sometimes this rewriting is better at explaining what our true motivations are, but not always.

                        I personally think that Dijkstra might have done this given his move from more practical problems to a focus on theory and formal methods. So my reading of only this early writing regarding goto is that he had more focus on pragmatics than theory.

                        Either way, it is quite interesting to make deep dives in old papers!

                    2. 2

                      if anyone has earlier citations explaining GOTO in terms of “jumping from one function to another” I’d be interested in them.

                      I want to read this post.

                      Here are quotes from Dijkstra’s original paper Go To Statement Considered Harmful that might be relevant:

                      Let us call such a pointer to a suitable place in the text a “textual index”.

                      This sentence is actually part of Dijkstra’s explanation of how we understand the dynamic behaviour of programs, but “textual index” could also be understood by some readers as the target of a goto.

                      As soon as we include in our language procedures we must admit that a single textual index is no longer sufficient. In the case that a textual index points to the interior of a procedure body the dynamic progress is only characterized when we also give to which call of the procedure we refer.

                      Here Dijkstra is using archaic language to describe what we now refer to as a stack trace. But if you misread the text and focus on the interpretation of “textual index” as a goto target while reading this sentence, you might pick up the impression that Dijkstra is talking about a goto into the middle of a procedure, from outside the procedure. That might sound like a stretch, but the internet is full of people quickly skimming a text (instead of reading it for comprehension) and misunderstanding what they read.

                    3. 16

                      Goto is not a horror. In most languages. I have a special hatred for it in C/C++.

                      It is sufficiently powerful enough to jump over control flow constructs, essentially destroying the block discipline of a program. Which is fine when the compilation target is something that has goto in it: assemble basic blocks out of everything and compile jumps out of gotos. But when you want to do more in-depth analysis it becomes a complete PITA.

                      Evidence for goto still being too powerful can be found in the fact that in most languages that came after, goto has additional restrictions.

                      JS has labels for break/continue, but no goto. And the note there is interesting:

                      Any break or continue that references label must be contained within the statement that’s labeled by label. Think about label as a variable that’s only available in the scope of statement.

                      Golang allows gotos, but:

                      A “goto” statement outside a block cannot jump to a label inside that block.

                      1. 3

                        Another example is Common Lisp, which has a special operator tagbody that takes a block in which any symbol at the top level is interpreted as a label, and inside the lexical scope of the tagbody, any use of the (go foo) operator behaves as a jump to any label foo. go can appear inside lambdas, and if the lambda is called further down the stack, it will unwind to get back up to the level of the tagbody. But trying to jump into anything is invalid. A typical CL implementation would have all looping constructs implemented as macros that expand into uses of tagbody and go.

                        1. 1

                          A typical CL implementation would have all looping constructs implemented as macros that expand into uses of tagbody and go.

                          Why?

                          1. 1

                            Well, why not? Is there a benefit to making looping constructs into special builtins rather than macros? Jumps to labels, even with restrictions, is enough to implement any looping construct you might want.

                            Quite a lot of stuff in Common Lisp can be implemented effectively upon lower level constructs in the language, and indeed, some parts of the language started as portable libraries for the language described Common Lisp the Language, 1st edition, before becoming part of the ANSI standard. The Common Lisp Object System, for instance.

                        2. 3

                          I didn’t realize JS had labeled break/continue until I was implementing support for some of the edge cases of it in jsc (when I was first working on JSC it was still much closer to just being kjs with a few basic compatibility quirks).

                          continue is restricted to only occur if the label target is a loop construct but break can apply to labeled blocks, switches, or loops, etc. Back in the early days while jsc was still mostly the kjs AST interpreter labeled continue/break were hilariously expensive because every break/continue with a label got a pile of hash lookups every iteration.

                        3. 13

                          The goto that Dijkstra is talking about is dead. Your language does not have it. Your language hasn’t had it for decades. You do not have Dijkstra-goto.

                          The goto in your modern language is not the one Dijkstra was talking about. Dijkstra was talking about a goto that could leap anywhere. Literally anywhere.

                          Your language does not have that goto. Your language has a goto that has been thoroughly constrained by the structured programming paradigm. You can not goto from one function into another, in the middle of an if statement, in the middle of a for loop.

                          $ cat hello.cpp
                          #include <stdio.h>
                          int main()
                          {
                              goto label;
                              if (0) {
                              label:
                                  printf("hello world\n");
                              }
                              return 0;
                          }
                          $ gcc -std=c++2a main.cpp
                          $ ./a.out
                          hello world
                          

                          Seems to work okay in C++20.

                          What language is he talking about? EDIT: Go.

                          1. 7

                            I think “goto from one function into another” is the most important part there.

                            Edit: though, even ignoring that, I think we can easily argue that C++20 isn’t much of a modern language.

                            1. 4

                              I think “goto from one function into another” may be another error in the rant. Dijstra’s original piece only said that high level languages shouldn’t have goto, he said it was fine in machine code (where you can indeed jump anywhere). I don’t know a high level language in 1968 where you could do this. The ones I checked are Cobol, Fortran 66 and Algol 60, where statement labels are restricted to function scope. Dijstra mostly references Algol in the essay.

                              1. 7

                                I don’t know a high level language in 1968 where you could do this.

                                BASIC is the most common example

                                edit: oh, PL/I is fun, its goto can be “nonlocal” in which case it behaves like C longjmp.

                                1. 1

                                  And as I recall, Dijkstra really really really hated BASIC.

                                2. 2

                                  Then I fall back on my argument that C++20 isn’t a modern language ^_^

                                  Or at the very least, the goto semantics are ancient.

                              2. 5

                                It may be even less known (apart from Duff’s device) that C’s switch+case is not a syntactically clean unique construct, but actually it’s arbitrary code with a bunch of gotos that you can place in horrible ways:

                                #include <stdio.h>
                                int main() {
                                    switch(1) {
                                        if (0) {
                                            default: 
                                            printf("this is valid and gets printed");
                                        }
                                    }
                                }
                                
                                1. 1
                              3. 5

                                Lookup the comefrom statement on wikipedia.

                                Now that is scary.

                                1. 2

                                  i still remember the linux kernel mailing list post where a new poster said something to the effect of “hey linus, i noticed gotos all over the kernel code, isn’t that a bad practice?”. linus did not flame him :)

                                  1. 2

                                    What are the most common uses of goto besides cleanup? In my experience, most of the defensible use cases for goto in C would actually be happier with destructors or defer, but I’m curious what else is out there.

                                    1. 1

                                      I’ve used it to implement a state machine for parsing HTML. I could have used a switch statement, but … it just didn’t seem right to me at the time (time being the late 90s when I originally wrote this code).

                                    2. 2

                                      I actually miss the C goto.

                                      Because I sometimes need an inline block that can be terminated early or repeated early depending on conditions detected inside and has special case for the first iteration. It’s bothering me to have to work around the lack of goto with do-while and sometimes even having to allocate a variable.

                                      I mean, I have just performed the comparison, the result flag is set, now I only need to tell the CPU to branch on it. But nooo, the language cannot express that.

                                      Does nobody write state machines anymore?

                                      Funnily enough, out of all popular languages, JS/TS has labelled loops that help somewhat. We should have them everywhere!

                                      1. 5

                                        Funnily enough, out of all popular languages, JS/TS has labelled loops that help somewhat. We should have them everywhere!

                                        Labelled loops are super common though? Fricking Java has labelled loops. So does Go. C# does not, but it has goto.

                                        Rust even added support for break-ing out of a non-loop block 18 months ago. Including breaking with a value (so pretty much early return for blocks): https://blog.rust-lang.org/2022/11/03/Rust-1.65.0.html#break-from-labeled-blocks

                                        1. 2

                                          Rust even added support for break-ing out of a non-loop block 18 months ago. Including breaking with a value (so pretty much early return for blocks): https://blog.rust-lang.org/2022/11/03/Rust-1.65.0.html#break-from-labeled-blocks

                                          2022-11-03

                                          I am probably a couple years behind on syntactic features of various languages, but having this added to Rust in 2022 kinda confirms my sentiment. It’s nice to know I am not the only one and it will get supported everywhere eventually.

                                          What strikes me is the almost apologetic tone of the announcement:

                                          This may sound a little like a goto statement, but it’s not an arbitrary jump, only from within a block to its end. This was already possible with loop blocks, and you may have seen people write loops that always execute only once, just to get a labeled break.

                                          It’s weird to see people apologize for adding handy control flow manipulation features.

                                      2. 1

                                        C still has longjmp, and it has its uses (although any actual use of it outside of e.g. bespoke exception handling deserves incredible scrutiny). So I wouldn’t go as far as to say that Dijkstra entirely succeeded in killing the older goto.

                                        1. 2

                                          In my 30+ years of C programming, the only time I’ve used longjmp() has been with recursive descent parsers and a 6809 emulator where nothing other than stack frames have been generated. I’d be hesitant to use it in any other context, as you can easily leak resources.

                                        2. 1

                                          I’m so glad we established the fact that Dijkstra won all the way back in the 1960s. And I’m absolutely delighted that we agree that modern goto is not terrible at all.

                                          1. 17

                                            That has nothing to do with goto. It could as easily be:

                                            if (err)
                                                break;
                                                break;
                                            
                                            1. 1

                                              Could it be? Would it be if there was no goto? Is break even valid outside of loops and switch? I don’t know the answers. I doubt it’s even possible to knows.

                                              But you’re right, I’m being unfair to goto. It’s perfect in every way. It’s the logic around it that is at fault.

                                              1. 5

                                                It’s not break vs goto, the core problem is the deceptive indentation.

                                                if (something)
                                                    foo();
                                                    foo();
                                                

                                                also doesn’t do what is expected

                                                if (something) {
                                                    goto fail;
                                                }
                                                    goto fail;
                                                

                                                would have a much better chance of being caught.

                                            2. 7

                                              Despite the name that’s not a goto specific bug. Could have been a return, break, continue or even a variable assignment that affected the control flow.

                                              1. 4

                                                I don’t think C’s goto counts as modern – it’s similar to BCPL’s goto from the 1960s.

                                                Tho at some point BCPL narrowed the scope of labels to the closest enclosing block, instead of the closest enclosing routine body, so you can no longer jump into a loop or past a declaration, which is a significant improvement compared to C.

                                                1. 1

                                                  This is a valid argument. I’m afraid my knowledge of languages is limited. The only ones I know have goto are C and Basic and they seem very similar to me. Are there any other languages with different gotos?

                                                  When I say “modern goto” I mean a goto in relatively modern codebases. Otherwise how do we even define it if there are no examples?

                                                  1. 2

                                                    When I say “modern goto” I mean a goto in relatively modern codebases. Otherwise how do we even define it if there are no examples?

                                                    It’s defined by the languages’ specifications.

                                                2. [Comment removed by author]

                                                3. 1

                                                  I’ll just leave this here:

                                                  https://perldoc.perl.org/functions/goto

                                                  FWIW I’m pretty sure you used to be able to jump to another subroutine in older versions of Perl. (I have a vague, possibly false, memory of the Perl 5.8 release pumpking telling me about it.)