Back

Goto (2007)

243 points2 yearsbeej.us
bodyfour2 years ago

Probably coming too late to the discussion, but commenting anyway...

The issue I think is that people today read "GOTO Considered Harmful" without really understanding the world at the time.

Other than simple integer FOR loops, basically all control-flow in the FORTRAN of those days was accomplished with GOTO -- and to numbered lines, not labels. Things we take for granted in all languages today like { code blocks } didn't exist.

Indeed the first thing you do when you try to understand code of that era is to print it out, get your markers out, and start drawing arrows all over the green-bar paper so you can start to make some sense of where control flow is going.

In that world, yes, GOTO was a big problem and its use was rightly replaced with the structured control flow that we all use today. But that world has also been nearly gone for 30 years now. It's completely unrelated to using a C "goto" statement with a well-chosen label name in order to accomplish some otherwise-awkward control flow.

On projects I've worked on I tend to develop a reputation for heavy goto use and I do so unapologetically. Sometimes it honestly is the cleanest way to implement something and I don't think it should be feared one bit.

Just because it was a bad idea to do everything with a GOTO 60 years ago shouldn't mean don't do some things with goto today.

GrumpyYoungMan2 years ago

> "The issue I think is that people today read "GOTO Considered Harmful" without really understanding the world at the time. ... Just because it was a bad idea to do everything with a GOTO 60 years ago shouldn't mean don't do some things with goto today."

Every time this comes up, I always encourage people to read an excellent analysis by David Tribble "Go To Statement Considered Harmful: A Retrospective" that goes over Dijkstra's essay line by line and explains what it means in a more modern context: http://david.tribble.com/text/goto.html It really should be required reading in CS programs.

I've used and still use goto in the few niches it makes sense to, primarily error handling and multi-level break in C/C++.

User232 years ago

> "Dijkstra later abandoned the search for program provability and turned instead to the study of techniques for correct program derivation"

It's odd how even today very few people in the "formal verification" world understand this distinction. Naturally verifying a derived program is trivial, since it's literally a proof by construction. I've attempted many times to explain this distinction, and they inevitably just start babbling about the limitations of the tooling they use. A sad case of man with a hammer syndrome and probably an illustration of my own failure to achieve clarity I guess.

> "However, to some extent Dijkstra's principle has not been fully realized when we observe the complexity that must be dealt with by real-world programming tasks, such as multitasking, multithreading, interrupt handling"

Dijkstra designed the first proven correct interrupt handler for a multitasking OS two years before the Goto Letter[1][2] so I think we can rest assured that he was aware of these issues.

[1] https://en.wikipedia.org/wiki/THE_multiprogramming_system

[2] https://www.cs.utexas.edu/users/EWD/transcriptions/EWD01xx/E...

wglb2 years ago

This is a good balanced article.

What is often missing from these goto discussions is the dialog between Knuth and Dijkstra on this topic.

There is another discussion between the two of them about a problem requiring no less than four stacks to understand.

0xffff22 years ago

Can you give an example of where you've used goto in C or a C-like language? Or a rough estimate of the number of times you've found goto to be the best solution?

I agree with your post in principle, but in practice I can't think of a single example where I've used a goto that wasn't eventually refactored to something better that didn't have the goto.

Edit: Probably the most common example (and one given early in the article) is deeply nested loops. This is a good example of where I'm very unlikely to use goto. Almost certainly, I will prefer to hide one or more of the inner loops inside of a function call.

jjav2 years ago

Think of everythin which in java you would put in the catch and finally blocks. Those are nearly always most cleanly handled in C code by doing a goto to the cleanup/return section at the end of the function.

You could work around this via tons of deeply nested blocks, but that makes the code very unreadable. The goto cleanup pattern makes the code intuitive and very readable.

I still have a printout somewhere of an application from the 80s, maybe a dozen pages in small font, every few lines it jumps via goto to somewhere else, depending on state (global variables, of course). It was a serious nightmare to figure out where the code is going. That was the historical context of "goto considered harmful". It sure was! Code like that doesn't exist today, the lessons have been learned. It had nothing to do with clean jump to a cleanup section (which is what a finally{} block is, after all).

davidwf2 years ago

It's a very common pattern for C memory allocation checking. A public example I know off the top of my head can be seen here: https://github.com/zedshaw/learn-c-the-hard-way-lectures/blo... (the implementation of the CHECK macro). That's in a C tutorial but I've implemented a version of that macro frequently.

Let's say you need to dynamically allocate two buffers in a function and want to make sure they are freed at the end of your call. You can use this macro like so:

  int two_bufs(int n, int m) {
    int *buf_1 = NULL;
    int *buf_2 = NULL;
    buf_1 = calloc(n, sizeof(int));
    CHECK(buf_1);
    buf_2 = calloc(m, sizeof(int));
    CHECK(buf_2);
 
    // ... lots of cool things with buf_1 and buf_2

  error:
    free(buf_1);
    free(buf_2); // Safe if null

  }
jefftk2 years ago

You have "free(buf_2); // Safe if null", but if CHECK(buf_1) turns into a "goto error", won't buf_2 be uninitialized? And so can take on any value?

+1
davidwf2 years ago
InfiniteRand2 years ago

It's good for a simplified version of exception handling for C (granted C does have setjmp/longjmp but that's more like the complicated version of exception handling)

If you hit an error condition in deeply nested logic and need to go to common error handling code or go to a recovery point, then goto makes sense.

Often times in these scenarios simplifying the logic is also an option, but that isn't always the case, especially when dealing with external interfaces that have a lot of potential error conditions that cannot be dealt with easily from the immediate caller (I'm thinking primarily of system interfaces, but also potentially library interfaces).

From what I've seen this is the only really good scenario for goto and I think the examples in the article more or less fall into this category of escaping from deeply nested error cases.

brundolf2 years ago

Makes me realize that the way some people use caught-exceptions is actually just reaching for goto in a language that doesn't have it

anfelor2 years ago

Another example not covered on the article is tail call optimisation. GCC is not very good at it and manually adding it using goto can sometimes speed code up by ~20%. Very useful when writing down a complex recursive algorithm that you can't easily express as a loop.

NtGuy252 years ago

If you have a dispatcher or an infinite state machine, say an emulator. Or even just anything in general with cleanup, you use goto to "goto ERRORCLEANUP". So you have one exit condition to clean up. This is recommended in CERT c.

throwaway092232 years ago

There's a good real world example of goto for progressive cleanup here: https://github.com/square/pam_krb5_ccache/blob/master/pam_kr...

throwawaylinux2 years ago

The Linux kernel has 28 million lines of C code, and 180 thousand goto statements.

1 every 160 lines is a goto, and that's just a naive count so includes all header and preprocessor gunk, whitespace, some generated C code, comments, etc.

If you're talking about more pure lines if C, it would probably be close to 1% of statements are goto.

It might not be everyone's cup of tea as the pinnacle of C programming, but it's a real highly used medium-large project that has coped well with a pretty high rate of change from a very wide spectrum of contributors. So it is a good real world example of open C code you can look at that is maintainable while making a lot of use of goto.

GrumpyYoungMan2 years ago

There are a few decent examples given by the Linus kernel devs on a LKML thread where an unfortunate soul wandered in and suggested that they refactor to get rid of GOTOs: https://lkml.org/lkml/2003/1/12/126

> "I will prefer to hide one or more of the inner loops inside of a function call."

That risks the additional unnecessary overhead of a function call, which can be expensive in a loop, and not all compilers are as good at optimizing as they should be.

mumblemumble2 years ago

What's wrong with the examples in the article?

bodyfour2 years ago

> deeply nested loops. [...] I will prefer to hide one or more of the inner loops inside of a function call.

And I would too... but sometimes there are too many local variables involved to do that cleanly. And, ultimately, if you are making your code less clean to avoid using a goto then you avoided unwisely.

> Can you give an example of where you've used goto in C or a C-like language?

Well there have been several fairly normal examples cited by others. Let me set out my stall and present a more controversial one.

Suppose I have a switch() block (which is sorta a glorified goto already!) and I have two cases that have the same epilogue. For example:

  switch (get_next_thing_to_do()) {
  case ACTION_A:
      do_action_a();
      goto common_post_action_cleanup;

  case ACTION_B:
      do_action_b();
    common_post_action_cleanup:
      cleanup_something();
      z = nullptr;
      x = x->next;
      // yadda yadda
      break;

   case ACTION_C:
      do_action_c();
      // NOTE: we don't need to do the "cleanup" here
      break;
   }
Now the first thing you should try is to put all of the shared code in its own function. Again, this isn't always practical if what you need to do effects a lot of local variables. It also means that those "cleanup" steps are now implemented far away from the rest of the logic which might hurt comprehensibility on its own.

Another option is to just suck it up, repeat the code twice, and then just trust that the compiler will clean it up. First, that won't be as attractive if you have to do it 10 times instead of twice. Second, you're now violating the DRY principal which to me is sacrosanct. Experience has taught me that if someone updates the cleanup code for ACTION_A they will forget to update ACTION_B at the same time. If you care that the two cases always end with the same epilogue you need to have a single copy of the code.

You can try to get back into compliance with DRY by using something like a C macro, but now you're not winning the war against ugly code.

Finally you could just put the cleanup code after the switch(), either by retesting the enum value (requiring it to be stored in a temporary variable) or by adding some other new cleanup_needed boolean flag. But now we're adding control flow that wasn't there before. I personally believe that doesn't help readability or maintainability. You're now just adding more variables to understand and probably getting worse generated assembly to boot.

While many programmers will look at this and feel vaguely icky about the presence of a "goto" if you really stop and look at the block of code it's actually quite readable...at least as long as the label and the goto are near each other. It is immediately apparent that two "states" are sharing some code and why.

This little example is obviously made up. However I've spent a lot of my career writing things like hand-crafted state machines and situations exactly like this (where two "states" are different but share the same epilogue) comes up all the time. State machine code like this is famously hard to read but often sharing code via goto like this is the least-bad option for expressing non-hierarchical control flow.

gsinclair2 years ago

I really like this answer, but I do think a flag “cleanup_required” could be a clean way to express the code as well, with “if cleanup_required …” after the switch block.

But yep, I have no complaints with the code as presented.

ghoward2 years ago

For a rough estimation, you can use my bc at [1] which uses goto extensively for safe and effective use of setjmp() and longjmp(), while having no memory leaks (to my knowledge).

When going through the code, though, be aware that some goto's are in #define's to make it easier to work with setjmp().

If you're lazy, it's about 10k loc with about 100-200 uses of goto.

[1]: https://git.yzena.com/gavin/bc

idiocrat2 years ago

The times I found I used the goto statements are retrials.

For example, if a file is locked for writing, I want to wait for 5, then 10, then 15 seconds, retrying each time, before giving up.

In such case I do not want the retrials loop to be my main control structure.

Instead, I jump from inside the IO exception catch block to the ReTry label, restarting the whole operation.

This makes the flow fairly clean.

slaymaker19072 years ago

Replying to infiniterand, a table of function pointers is going to generally have way more overhead than an indirect goto. An indirect goto is essentially a single assembly jump instruction. This is also often faster than a while loop plus switch since it is friendlier to the branch predictor.

InfiniteRand2 years ago

I'm sure there's probably a reason for doing things this way, but it seems like that code would make more sense as a function pointer table and a series of functions. Was that not preferred for aesthetic reasons or for some specific technical reason?

codr72 years ago

Speed.

Your suggestion adds indirect memory access plus function call, for every instruction.

I've tried every other way I can think of, and nothing runs faster from my experience.

adrian_b2 years ago

Few programmers use goto, because they have been indoctrinated against it and they have no understanding about when goto is bad and when goto is good, but there are many cases where using goto is better (i.e. more clear and/or faster) than what most programmers write now in such occasions.

In some of those cases there are alternatives that could be better than goto, but they cannot be used because they need features that are missing from the programming languages popular now.

One example is state machines. Many programmers when having to implement a state machine use a state variable, e.g. an enumeration and a big "switch" or "case" structure, depending on the language, with a branch for each state.

This kind of implementation follows the method taught now to avoid goto's by adding unnecessary variables that are used to store some state when some condition is detected with the purpose to make a jump later with an extra if or switch instead of doing a jump with a goto immediately when the condition has been detected.

This method of eliminating goto's is pretty stupid, because it not only adds useless variables but it also doubles the number of conditional branches, because each

if (condition) goto label;

is replaced by:

if (condition) variable = value;

...

if (variable == value) {...} or switch (variable) {...}

Besides the accesses to non-cached memory, the conditional branches are the main causes of low performance, so doubling the number of conditional branches can result in much worse performance.

When the second conditional branch is a "switch", that is even worse, because it is typically implemented as an indexed jump, which will be frequently mispredicted.

Instead of using a state variable and a "switch" with branches for states, the right implementation is to have sequences of statements for the states, with the first statement in each sequence labeled with the name of the state.

During each state, when the condition for the transition to another state is detected, a goto is executed, with the name of the next state.

Anyone who believes that this is less clear or more difficult to read than the variant with state variables is delusional, because in the variant with goto's you see immediately the effect of a transition condition, while in the variant with state variables you just see some value being stored and you do not know its purpose until possibly much later when you discover that it is tested for a second conditional branch. Even after you see this use, you are not certain that this is the only place where the variable is used, it might also have other uses and you might need extra time until you are certain about what the program really does.

A more elegant solution for state machines is possible only in languages where tail call optimization is guaranteed.

In such languages each state may be implemented as a separate function and the goto's may be replaced with function invocations in final positions, which are transformed by the compiler into jumps.

While not so many programmers need to write state machines, there are much more frequent uses where goto is superior, e.g. for error management in cases where exceptions are inappropriate, but describing when, why and how would be long.

In general all the problems for which goto was considered harmful are not solved by eliminating goto but by restricting its behavior.

There was a series of research articles by various authors, published between 1970 and 1975, whose conclusion was that the right kind of goto should be allowed to make only forward jumps and that the labels must have a scope restricted to the block in which they are declared. Therefore a goto should be able only to exit from a block but not to enter a block.

An example of a programming language that had this restricted form of goto was the language Mesa, from Xerox.

In C however, it would not be possible to restrict goto only to forward jumps without simultaneously adding guaranteed tail call optimization.

jjtheblunt2 years ago

state machines a la finite automata, which (for examples) are generated by compiler parser generators like yacc ?

CJefferson2 years ago

I agree with you completely.

I once made a poster (I wish I'd kept it) where I was tracing out the behaviour of a single mega function with around 50 labels in it, and gotos all over the place.

I worked on a reimplementation, slowly picking apart the function into subfunctions, while loops, recursive function calls, etc. Took me about 2 weeks.

kazinator2 years ago

I could refactor a self-contained 50 label goto graph into a network of tail recursing functions pretty quickly; as little as an hour, in the absence of nasty confounding factors.

If the goto graph implements a calculation with no side effects like I/O, my network will be all pure functions, too.

Completely beyond the reproach of any CS academic. :)

There is a mechanical way to do this.

1. We turn all of the function's local variables into function arguments. Each of our tail recursive functions will take these arguments.

2. Every labeled node becomes a function.

3. Every goto becomes a function call invoking the function that its target label was turned into, passing in each of its own argument to the corresponding parameter of that function. The function's return value is immediately returned.

4. Every variable assignment before a goto is turned into an argument expression in the function call which passes the new value to the corresponding argument. E.g. instead

     a++; d *= 2;
     goto foo;
we have

     return foo(a + 1, b, c, d * 2, e, ...);
Once you have this working, then you refactor. For instance, if you have a function like this:

     int foo(int a, int b, int c, ..., int z)
     {
        return a + c;
     }
which does not pass its arguments to another tail-called function, then you can eliminate all of the arguments and just turn it into:

     int foo(int a, int c)
     {
        return a + c;
     }
and of course edit all of the calls to foo accordingly, and simplify those places also. In this way, you will "discover" simpler functions that work with just a subset of the state transfer.
prox2 years ago

You maybe the right person to ask… Aren’t function / functioncallers a form of goto?

CJefferson2 years ago

Technically yes, all code eventually compiles to goto, or goto equivalents (simplifying slightly)

However, the idea behind "goto considered harmful" is almost all code can be written with function, loops and if/else statements and get just as efficient, and much, much easier to understand.

kazinator2 years ago

All goto code can be mechanically translated into a network of mutually tail-calling functions arranged into an identically shaped call graph that is hardly easier to understand.

What you can do with goto-based code is refactor it into a form that obeys certain conventions such that it is easy to mechanically translate to such a tail-call newtork, but then stop short of actually doing it.

Then when reading the code, just pretend you're looking at function calls.

munificent2 years ago

That must have been so satisfying once it was done.

blntechie2 years ago

My first job was writing VB.NET code and it was hammered to us not to use GOTO. Once for a critical production bug, using GOTO was the quickest solution to fix and I used it with a comment to not fire me. And that code lived on for years.

ehutch792 years ago

Sometimes a little bit of poison can make a great medicine.

Goes both ways though.

moffkalast2 years ago

Yeah to those still adamant about it, if you've ever used a:

- function call

- if sentence

- any kind of loop

You've used a GOTO under the hood. I hope you can live with yourself, you monster :)

brundolf2 years ago

But there's a reason we come up with sanctioned, constrained concepts built atop more powerful lower-level ones. Constraints in code benefit maintainability, correctness, and sometimes even performance.

"Yeah to those still adamant about it, if you've ever used statically-typed code, you've used untyped assembly under the hood. I hope you can live with yourself, you monster :)"

agent3272 years ago

You put a smiley so I suppose it's possible you know better, but this kind of shallow, thoughtless critique is just painful to read, especially when following such a well-presented, historically accurate description of the debate surrounding goto. This is especially true given that Dijkstra himself refutes your argument, such as it is, in the opening paragraph of his seminal paper.

masklinn2 years ago

> You've used a GOTO under the hood.

That it's under the hood is the point of using constrained control-flow control. It's significantly easier to reason about `if` and `while` and `for` than about `goto`. That is basically the same reason functional programming have more HoFs than `fold`. You can do everything with `fold` and that's a problem because it creates way more cognitive overhead for the reader, they can't know what the code is trying to achieve until they get all the details, nor is the compiler able to check anything.

Likewise goto.

GrumpyYoungMan2 years ago

What's even worse is that, at the machine language level, condition and unconditional jumps are all that there are; we've all been using GOTOs constantly without even knowing it. :)

andi9992 years ago

The real hidden gotos are:

- state machines - early return of a function

1-more2 years ago

a case statement is the one where the GOTO peeks out the most, imo.

CodeMage2 years ago

This is my pet peeve with how they teach kids programming. Granted, I only have anecdata on that topic, gleaned from how they teach my own kid at school, but I still can't shake the feeling that we're doing them a disservice by making them skip straight to JavaScript or Python.

I wonder what it would be like if kids started with BASIC. And not Visual Basic or any other modern, structured flavor. No, I mean the ancient stuff:

    10 PRINT "All your base are belong to us!"
    20 GOTO 10
Limited in what it can do, simple to learn, with just enough fun stuff in it (like PLOT, DRAW, INKEY$, INPUT, etc.)

Then go on to assembly language, again on some very limited and simple machine, along the lines of the good old ZX Spectrum 48 or Commodore 64.

Then move on to something like C or Pascal. Or even Python and JavaScript if you want. But the idea is that going through BASIC and assembly, they'll learn two important things: 1) how computers actually work (even though it's super-simplified, it should still be useful), and 2) why you need structured programming.

Or maybe I'm just being naive. I really would like to see if that works out, though.

gsinclair2 years ago

I teach high school CS and have eventually come to the same insight, and found that novice programmers do indeed understand primitive BASIC much more easily than Python. Python seems simple to us, but there is so much abstraction even in its base-level syntax. I am gradually redeveloping my course materials to use BASIC (via replit.com) in the early stages.

ouid2 years ago

I thought the problem with unstructured goto is that it is extremely hard to reason formally about code that uses it.

1vuio0pswjnm72 years ago

SNOBOL, the non-numerical computing counterpart to FORTRAN, also uses GOTOs, exclusively.

I still run spitbol. Regardless of its assembly-like control flow, I think the pattern matching beats PCRE, and Lua's implementation of LPeg.

westcort2 years ago

What I want is a language that consists ONLY of GOTO statements. The Turing tape concept indicates that it should be possible.

schoen2 years ago

Subtract and branch if negative is more or less that:

https://en.wikipedia.org/wiki/One-instruction_set_computer#S...

rstuart41332 years ago

> Indeed the first thing you do when you try to understand code of that era is to print it out, get your markers out, and start drawing arrows all over the green-bar paper so you can start to make some sense of where control flow is going.

The first thing I do is change the indentation to reflect the structure. The basic blocks then become obvious. If you don't do that (and most people in the era didn't), it's about as hard to read as left aligned C code. With it, it's not that much harder to read than indented C code.

Unfortunately not indenting the code just reflected a bigger problem: programmers back then were totally oblivious to the structure indenting revealed or how they could be used to reduce the difficulty in reading the code: basic blocks, limited scopes and the like. It's not that they were lazy, it's that they didn't understand how code is built.

Dijkstra taught computer science. That is what he was up against. At the time, all programming languages used goto almost exclusively for flow control. In some ways, it is the simplest way to do it. One construct did the job of the multitudes we use today: for, while, until and even recursion. However if you didn't follow some simple rules the goto's invariably ended up as spaghetti and the end result ended up being there was far more spaghetti in production than structured code.

The two simple rules are: 1. backward goto's were only for loops, and 2. you must not goto inside an inner basic block. These are exact same rules for and while loops with {} blocks enforce. But it takes some self discipline to apply those rules, and no one is going to do it if they don't understand why they are important. Such understanding requires good high level intuition about how code is structured. It took a long while to teach such intuition. If you got rid of the goto's by replacing them with higher level constructs like for and while, you not only got rid of the problem - you also forced young students to think in terms of code flow. That is what Dijkstra was arguing for in "Goto's considered harmful".

Today, times have changed. You very rarely see a badly structured goto in the Linux kernel code, even though C allows it. I'm not sure why that is - because I'm pretty sure if Dijkstra hadn't won out and Javascript and it's ilk still had goto's then shudder. The coding standards in web programming is a cluster fuck as it is: I swear there isn't a week go by when I don't come up against a broken web page. Just this week I was forced to fire up the browsers js debugger to change a field on a linkedin.com form, and manually do "$0.value = 'yes'" to move on. ffs.

But yes, in projects with coding standards as high as the Linux kernels, everybody that (is allowed to?) contribute seems to have a very good understanding of how to make code that others find easy to read and understand, a ban on gotos would be counterproductive. Despite the discipline they require, gotos can be used to make the code cleaner and shorter. You see numerous examples of that in the kernel.

enriquto2 years ago

Coming from assembly language, I always found the anti-goto sentiment rather cute. A beautiful restriction, but ultimately arbitrary. Like writing poetry. Or those novels that do not ever use the letter "e". Why would an otherwise sane person write code with "rep" and without ever using "jmp"?

jcranmer2 years ago

What is done is that people have taken the useful cases of goto, categorized them, and use renamed keywords for different cases. In many modern language, there are four keywords that all imply a general feature of goto: break (go to the end of the identified block), continue (go to the increment block of the identified loop), early return (go to the function cleanup code), and throw (go to something complicated). Indeed, with labeled break and labeled continue, you can actually produce almost any arbitrary control flow [1].

The last major use of goto in C is to recreate what many languages solve via some form of destructor, defer statement, with statement, or some other mechanism that has the compiler insert specified cleanup code on all exiting paths from a block. If you use such a mechanism, then it becomes impossible for the programmer to accidentally skip over such cleanup code, whereas a C function using goto might have the programmer write an early return instead of the goto by accident and skip it. And this is why there's a strong anti-goto sentiment: we have better tools [2] to achieve the same ends that are less error prone than goto is. And when you have the better tools available, why shouldn't you ban the worse tools?

[1] The one thing you can do with goto that you can't do with labeled break/continue is create irreducible control flow graphs (essentially, a loop with two entry points, thus having no dominator within the loop itself). On the other hand, I'm pretty sure that murdering a programmer who creates an irreducible control flow graph is considered justifiable homicide, so no harm is lost by outlawing it.

[2] In languages other than C, though.

slaymaker19072 years ago

I would disagree with defer being better than goto. Some people prefer it, but I think in a lot of ways it makes your control flow harder to see than just a few cleanup blocks at the end plus goto.

Jtsummers2 years ago

It's a tradeoff.

defer in Go is more like a try/finally statement in other languages, there's a guarantee that the deferred code will run even when there's a panic in the intervening code:

  handler = open(something);
  defer close(handler);
  // something causing a panic
Should be the same as:

  handler = open(something);
  try {
    // something causing an exception
  } finally {
    close(handler);
  }
This is not necessarily true of goto, you have to be more deliberate and disciplined (and discipline doesn't scale) to ensure that when your code hits an error you go to the cleanup section, if you miss even a single case and return early, you will not do the cleanup. Both defer and finally eliminate that potential error.

The interesting thing (to me) about defer is that it promotes making explicit what's implicit in languages like C++ with RAII. In C++ with RAII, handler would be closed up at the end of the lexical scope but it's implicit, Go makes you explicitly state that you intend for it to be closed using a defer. But the defer, stylistically not by requirement, being near the initialization is, arguably, clearer than the goto or the finally statement. It's also less error prone, you can scan the function and see that someone left out a defer close(handler). But if you use a cleanup block at the end (either with try/finally or with gotos) you have to jump back and forth between the top and bottom of the function in order to see that everything has been cleaned up and in the correct order.

+1
slaymaker19072 years ago
throw_m2393392 years ago

> Coming from assembly language, I always found the anti-goto sentiment rather cute. A beautiful restriction, but ultimately arbitrary. Like writing poetry. Or those novels that do not ever use the letter "e". Why would an otherwise sane person write code with "rep" and without ever using "jmp"?

There is no need to use GOTO when a language has functions/procedures and exceptions.

Of course it makes sense in Assembly or more rudimentary languages, or in specific cases for speed optimization (getting out of multiple loops).

However, I must admit, sometimes I'm sick of the way Go "deals" with errors so I resort to GOTO to manage HTTP errors in my HTTP request handlers.

mumblemumble2 years ago

One thing a C-like goto has over exceptions is that you can only jump around within a single lexical scope. That restriction is a big win for readability.

Exceptions use a dynamic scope to decide where to jump to, so, in the general case, it's a lot harder to understand how they will affect program behavior by simply reading the code. That's a major reason why using exceptions for non-exceptional control flow is widely considered to be evil.

lmilcin2 years ago

> There is no need to use GOTO when a language has functions/procedures and exceptions.

There is no need for exceptions either. Everything can be done with error code.

Just because there are redundant mechanisms to achieve same result does not mean one of them must be abolished.

msla2 years ago

> There is no need to use GOTO when a language has functions/procedures and exceptions.

The usual example of when goto saves more trouble than it causes is breaking out of a nested loop early. You can avoid goto in that scenario with a sufficient number of flags, but flag-heavy code tends to be harder to read than a couple apposite gotos. Even better is Perl-style named blocks, though.

srott2 years ago

> There is no need to use GOTO when a language has functions/procedures and exceptions.

... and defer

dragonwriter2 years ago

> and defer

Which “defer”? The go panic/defer pair is, while structured differently, functionally equivalent to exceptions.

+2
0xFACEFEED2 years ago
mumblemumble2 years ago

> A beautiful restriction, but ultimately arbitrary. Like writing poetry.

This assessment cuts pretty close to the gist of (my reading of) Dijkstra's famous paper that kicked off the anti-goto sentiment, once you take some time to digest the entirety of the paper and consider the context in which it was being written.

I'd also like to throw out there, though, that the "go to" statement he describes is a "go to" statement that functions like the one in programming languages from before 1968. Which is a goto statement that, I'm guessing, very few of us have ever used. Early versions of BASIC - the "20 GOTO 10" variety - are maybe the most well-known example nowadays. If you've spent much time with a Commodore 64 or a 1st-generation IBM PC, you're probably familiar.

He wasn't criticizing C's goto. Not just because C hadn't been invented yet. His most lucid, damning criticisms of goto in higher-level languages can't really be leveled against C's goto statement, because C's goto simply doesn't exhibit the features he's criticizing.

jsmith452 years ago

Right. He was criticizing the version where one subroutine might jump into the middle of another subroutine.

That sort of thing can make safely modifying the second subroutine require knowing if any of its labels are used elsewhere in the program.

It is basically impossible to reason locally when developing like that, unless you first verify that the only uses are local. (Or have applied strict coding standards such that only labels specifically identified can be used as non-local goto targets, etc.)

That sort of thing was extremely normal in say early assembly programming where your code size was so restricted, you needed to reuse code whenever possible. And many early languages brought that sort of capability along with it.

I'd expect Dijkstra's feeling on C's goto would probably be like: Probably a good idea to avoid it when the other control structures work well, but barring silliness like trying to code a large program in a single function, using C's goto for cases not well handled by the other control structures is fine, since it is constrained to local use only.

pajko2 years ago

There's setjmp() and longjmp()to do that :)

marcan_422 years ago

setjmp() and longjmp() only work upwards through the call stack. They're basically an awkward version of exceptions. You can't jump into a subroutine that isn't already executing with them.

naniwaduni2 years ago

Goto aversion, like single-entry/single-exit and everything anyone has ever said about 3rd-to-5th-generation programming languages, addresses a concern that hasn't been relevant in decades and that most modern programmers have no context for whatsoever.

The words have straightforward meanings, unfortunately, so they've since been recontextualized into settings where their prescriptions can still be followed, but the rationale doesn't hold up at all.

amelius2 years ago

Goto can, conceptually, really mess with the semantics of initialization and finalization of local variables.

zabzonk2 years ago

Indeed, which is why C++ forbids (and the compiler checks) such jumps.

userbinator2 years ago

I started with Asm too and probably have much the same thoughts on goto --- HLLs provide if/else and loops to simplify code, so use them when it makes sense, but when the control flow is such that the simplest solution doesn't fit within the constraints of HLL flow structures, then goto makes perfect sense.

Many of those who started with HLLs probably don't realise the machine can be far more flexible and powerful beyond their constraints. It's all about using the appropriate level of abstraction.

samhw2 years ago

> Or those novels that do not ever use the letter "e".

As a note on this, I think the novel you're talking about is Georges Perec's 'La Disparition'. Supposedly the lack of an 'e' is symbolic of the loss of his family in the Holocaust. Anyway, I always thought one of the greatest feats of the English language was the guy who translated the novel to English, also omitting the letter 'e'. Absolutely mindblowing talent.

ddingus2 years ago

>also omitting the letter 'e'. Absolutely mindblowing talent.

Unreal! I'm going to have to go read a passage or few. Thanks for mentioning it.

recursive2 years ago

Also Gadsby by Ernest Wright

Symmetry2 years ago

Likewise using local variables instead of global variables. The point of structured programming is that it's less flexible than gotos are. Decreasing the flexibility of programming constructs means that the reader has fewer possibilities to consider and hence makes the result easier to reason about.

Beyond structured programming you might have object oriented programming that uses restricted access to the object's members to enforce certain parities on them that make the object's behavior easier to reason about. Or functional programs that restrict the persistent mutable state and make programs easier to reason about in another way.

kristopolous2 years ago

Bringing it more generally, it's a powerful tool that's simple to use.

This means it is ripe for abuse by amateurs and fools and from that comes its reputation.

It's a shame that dogmas are developed against the use of such things instead of creating a culture of appropriate use.

You see it against say php, jquery, and even gof design patterns these days.

It's bullshit. Just because some buffoons do stupid things with something powerful it doesn't mean all people that use it are buffoons

IshKebab2 years ago

It's not at all like that. `goto` leads to hard to follow, bug-prone code.

> Why would an otherwise sane person write code with "rep" and without ever using "jmp"?

They wouldn't. Nobody is suggesting that you should avoid `jmp` in assembly. I think maybe you missed the point? The whole "avoid goto" thing is talking about higher level languages than assembly that provide proper flow control primitives like `if`, `for`, `switch` and so on.

efaref2 years ago

Avoiding jmp in assembly is a sensible thing to do. jmp may stall the pipeline if the branch predictor is wrong. Better to use cmov when you can.

And if we're being silly, you only really need mov[1].

[1]: https://github.com/xoreaxeaxeax/movfuscator

IshKebab2 years ago

That's a completely separate architecture-specific micro-optimisation.

mytailorisrich2 years ago

It's not arbitrary.

The goal is to improve quality by making the code better structured and easier to follow.

For instance, MISRA C rule 15.1 that states that goto should not be used explains: "Unconstrained use of goto can lead to programs that are unstructured and extremely difficult to understand". Indeed, in practice that's often the case.

enriquto2 years ago

I agree with you; and I keep a printed copy of the MISRA C document on my nightstand for serene reading before sleep (together with the JPL C rules). Yet this point 15.1 does not forbid at all to use goto, just to use goto in an unconstrained way. Thus it is more a warning than a rule.

In the same spirit, you can also enjoy Knuth's "structured programming with goto statements" for a clean programming style whose control flow is based on goto.

panax2 years ago

And yet we were recently forced to change a lot of code which did use goto in a constrained way(for error handling only), because someone decided to make 15.1 required instead of advisory and take 15.1 too literally (they also made 15.5 no multiple returns required as well) and some of the results were worse than if someone had used goto in an unconstrained way.

lelanthran2 years ago

> For instance, MISRA C rule 15.1 that states that goto should not be used explains: "Unconstrained use of goto can lead to programs that are unstructured and extremely difficult to understand"

Sure, but constrained uses are fine, right? So why blanket-ban it?

metarox2 years ago

MISRA doesn't ban it, that rule is only Advisory which pretty much means you can ignore it. Rules 15.2 and 15.3 (Required) define how you can use goto if you decide to use it.

Rule 15.2 The goto statement shall jump to a label declared later in the same function (forward gotos only)

Rule 15.3 Any label referenced by a goto statement shall be declared in the same block, or in any block enclosing the goto statement (can't jump a scope level lower than current [jumping in an if block from outside] or from 2 identical but separate scopes [from one if to another independent if])

Personally, I like the Linux kernel model for goto which fits these constraints. Reading code like a shopping list and jumping to the proper cleanup point is much easier to read than having 3-4 if else levels and having to scroll the screen to make sure resources are cleaned at all levels, etc. Nothing beats making the mental model of code smaller to keep in my head and readability in my book so I'll take that approach over applying holy war-ladden absolute rules.

Note that using gotos also jives well with Rule 15.5 requiring a single exit point in functions (Advisory).

Edit: as panax wrote somewhere in this thread, CERT-C rules even recommend it

+3
lelanthran2 years ago
buescher2 years ago

At least one of the versions of MISRA explicitly allows for the use of goto to break to cleanup code at the end of a function, if I remember correctly, and another is much more strict, but I don't have any copies of MISRA at hand at the moment.

zabzonk2 years ago

Well, in C you very rarely need it, but in assembler (particularly the 8-bit Z80 and 6502 that I've written raft-loads of code for) you definitely do. Of course, something like a CALL is better, if you can manage it.

sys_647382 years ago

It's mostly used for error bailing as nested if can be pretty dense to read and error prone.

formerly_proven2 years ago

goto is the idiomatic way to emulate RAII in C. It's everywhere.

+2
zabzonk2 years ago
scoutt2 years ago

I kind-of agree with parent, but I would define it "subjective" instead of "arbitrary".

mytailorisrich2 years ago

Well, as per my previous comment it's not subjective, either.

It's a "best practice" in the correct sense of the term. I.e. in most cases it is both not needed and clearer, more structured code may be written without using it.

Of course, as for anything, it is useful in a very few cases, emphasis on "very few".

+1
scoutt2 years ago
mysterydip2 years ago

Isn't the key word in that statement "unconstrained"? Goto is a tool like any other that can be abused.

mytailorisrich2 years ago

That's why there is this rule to constrain it!

It is pragmatic, practical, and clear to have a rule not to use it. This forces people to make the effort to structure their code. Now, if it so happens one day that the result is indeed worse than using goto then a conscious decision can be made to make a specific exception. Otherwise, this is the sort of thing that will creep up and cause unless discussions.

heavenlyblue2 years ago

Even in assembly there's position-dependent and position-independent code.

wruza2 years ago

And jumps relative to cs or ip, which serve both. PIC idea is not related to goto.

da_big_ghey2 years ago

It is some good point that goto has many helpful uses, it is when people use it for working around when control flow should be re-factored instead that it becomes harmful. Like on many other things it is no issue in moderation, only in excess.

0xTJ2 years ago

Blanket rules like "never use `goto`" are generally not great. A big part of the job of a software developer is knowing when to use what bits of a language.

Sometimes code with `goto` is simply easier to understand. You have to be careful, but dismissing it completely is throwing out a tool.

mikevm2 years ago

People who give blanket rules such as never to use `goto` clearly do not understand where this idea comes from or why it should not be used. Many years ago in my undergrad I submitted a programming assignment in C which used `goto` for cleanup, as is customarily done in the Linux Kernel and other C software and I had points taken off for using `goto`. Heh.

SAI_Peregrinus2 years ago

Should've used setjmp/longjmp to do the same thing. Make it even harder to reason about.

munificent2 years ago

> People who give blanket rules such as never to use `goto` clearly do not understand where this idea comes from or why it should not be used.

The idea comes from Dijkstra and he sure as hell did consider it a blanket rule: "I became convinced that the go to statement should be abolished from all 'higher level' programming languages (i.e. everything except, perhaps, machine code)."

bgorman2 years ago

C is portable machine code :)

krapp2 years ago
munificent2 years ago

Not in Dijkstra's time it wasn't.

TheCapn2 years ago

It's a rule given to those still learning the craft. Some people just don't realize that once you gain a level of expertise you should question the rules you were given and make educated decisions on whether those rules were just guard rails to help you learn or actual constraints put in for a real purpose.

When I was early on in code I abused the GOTO, once I learned it was taboo, I was forced to learn how to structure functions/statements in a way that flowed smoothly and created readability in the code that wasn't there before. Now that I understand the pitfalls, I use goto primarily in the "Bailing Out" form described int he link. To try and force the code to do what it does without the goto would likely create a bigger mess as I'd just be nesting statements beyond ever growing if-else branches designed to kick out when failure conditions occurred.

atq21192 years ago

A lot of software engineering techniques are more about managing mediocrity than fostering excellence. They go overboard with the prescriptions. This may well produce better results when you have an army of mediocre developers.

It tends to frustrate excellent developers though, so it's a tricky weapon to wield.

Of course, it also tends to frustrate those who only think they're excellent.

GuB-422 years ago

I think we must distinguish between forward and backwards goto. And whether or not you are entering blocks.

If you are not entering blocks, forward goto is usually fine, and for me, there is no good reason to think is is worse than break, continue and return. In fact, I would more readily ban mid-function return than this kind of goto.

Backwards goto is when you get spaghetti code, there is some use for it, but it is very easy to make a mess.

Entering blocks is particularly bad because it breaks both flow and initialization, I think if there is one kind of goto that should be considered harmful, that's the one, these can even break compilers. But as always, if there is a good reason to do it, why not, but you need a lot of convincing.

garenp2 years ago

This is generally my sentiment as well. The reason to avoid using goto is if it makes following the flow of execution for us humans difficult, which typically happens when jumping backwards.

Using goto judiciously to more easily escape a deeply nested block of code and/or jump to a cleanup section is not hard to follow.

JKCalhoun2 years ago

> Multi-level Cleanup

Used all the time a decade or so ago when writing code that made heavy use of Apple's CoreFoundation. CF could return nil (failure) for many operations, and memory management at that time was left up to the programmer.

// Not real code, approximated from recollection

bool createNestedCFThing () {

   CFDictionary *thing = nil;
   CFArray *arrayObj = nil;
   CFString *string1 = nil;
   CFString *string0 = nil;

   string0 = CFCreateString ("Hello");
   if (string0 == nil) {
      goto bail;
   }
   
   string1 = CFCreateString ("World");
   if (string1 == nil) {
      goto bail;
   }

   arrayObj = CFCreateArray ();
   if (arrayObj == nil) {
      goto bail;
   }

   if (CFArrayAddObject (arrayObj, string0) == false) {
      goto bail;
   }

   if (CFArrayAddObject (arrayObj, string1) == false) {
      goto bail;
   }

   thing = CFCreateDictionary ();
   if (thing == nil) {
      goto bail;
   }

   if (!CFDictionaryInsertObjectWithKey (thing, arrayObj, "somekey")) {
      goto bail;
   }
bail:

   if (arrayObj != nil) {
      CFRelease (arrayObj);
   }

   if (string1 != nil) {
      CFRelease (string1);
   }

   if (string0 != nil) {
      CFRelease (string0);
   }

   return thing;
}
mkipper2 years ago

This is the most common form of error handling used in the Linux kernel (at least the parts I look at). Although you'd normally have one error label for each allocation in reverse order, so you can jump to the cleanup step for the most recently allocated resource and pass through the other labels and clean them up as well. So in your example, it would be something like:

  bail_string1:
    CFRelease(string1);
  bail_string0:
    CFRelease(string0);
  bail_array:
    CFRelease(arrayObj);  
  no_bail:
    return thing;
vlovich1232 years ago

Manual memory management is bad but you can't escape it in C so `goto` is mildly useful there for that purpose. In all other languages you get better ergonomics.

* In Rust you wouldn't need any of that boilerplate because lifetime is automatically managed (& Rust has Objc bindings).

* If you turn on ARC & use the NS types you don't need this (they map to the same thing with no overhead for these primitives, so there's no good reason not to do this unless you are just being masochistic).

* In ObjC++ you should still use ARC. But if you felt masochistic, you could do:

    auto string0 = unique_ptr<CFString, CFRelease>(CFCreateString("Hello"));
    auto string1 = unique_ptr<CFString, CFRelease>(CFCreateString("World"));
    auto arrayObj = unique_ptr<CFArray, CFRelease>(CFCreateMutableArray(kCFAllocatorDefault, 2, kCFTypeArrayCallBacks, kCFTypeDictionaryKeyCallBacks, ));
    auto thing = unique_ptr<CFDictionary, CFRelease>(CFCreateMutableDictionary(kCFAllocatorDefault, 1, kCFTypeDictionaryKeyCallBacks, kCFTypeDictionaryValueCallBacks));
    if (!arrayObj || !string0 || !string1 || !thing) {
      return nil;
    }
    if (!CFArrayAppendValue(arrayObj.get(), string0.get()) || !CFArrayAppendValue(arrayObj.get(), string1.get())) {
      return nil;
    }
    if (!CFDictionaryInsertObjectWithKey(thing.get(), arrayObj.get(), "somekey")) {
      return nil;
    }
    return thing.release()
In C++ you could also use scope guards if you didn't like the `unique_ptr` stuff (not part of the stdlib yet, but it's not hard to roll your own).

The point I'm trying to make is that there are much cleaner ways of obtaining that result in whatever language you choose. If you do that, maybe you too won't be responsible for a goto fail security flaw [1].

EDIT: BTW. This advise isn't out of the blue. This was 100% a strong culture even within Apple 5 years ago. If you're concerned about performance of ARC, consider that Apple dogfoods it internally for nearly every part of the OS. The only pieces it's not used are for large historical codebases where the migration cost is a factor. Performance of ARC vs manual just isn't something anyone looks at. For the historical codebases usually one would use the ObjC++ approach instead.

[1] https://nakedsecurity.sophos.com/2014/02/24/anatomy-of-a-got...

lmilcin2 years ago

This is my favorite use of goto. Not only it makes it more readable and easy to ensure the cleanup is only executed, it results in efficient binary representation which is important for some of the embedded stuff I am doing.

hyperman12 years ago

There are a few situations like this, where a bad practice is in a specific case good enough: Using goto to bail out multiple levels, using SHA-1 as a non-secure hash, ...

The problem is these things waste social bandwith: Someone reading your code for maintenance or code review will first declare the code bad (GOTO is ugly! SHA-1 is insecure!), then you have to signal to them that it's actually OK in this case, then they have to convince themselves that actually it is OK.

Hence I only do these things if they are overwhelmingly good. They have not only to be worth it to be written that way, but also be worth it to do the repeated discussion that they are worth it every few months. They deserve a huge comment, containing benchmarks or proof of security or whatever. If the construction is only slightly better, I go with the slightly worse construction.

skipants2 years ago

> The problem is these things waste social bandwith: Someone reading your code for maintenance or code review will first declare the code bad (GOTO is ugly! SHA-1 is insecure!), then you have to signal to them that it's actually OK in this case, then they have to convince themselves that actually it is OK.

This is something that changes over time as the new convention gets reinforced. If you don't push back it will never change. In other words, the conventional way of doing things needs to be spread organically.

A lot of conventions work that way. Since I'm in Web Dev, one example I can think of is the change from fat models to service objects in MVC. Fat models used to be seen as the way of doing things. But, now that many of us have encountered giant balls of mud caused by this pattern, we've adapted a new convention and moved on.

If you can provide good uses of goto in the wild and push back against opposition the same thing will happen, in my opinion. Eventually the opposition goes away.

SAI_Peregrinus2 years ago

> using SHA-1 as a non-secure hash,

That I actually partly disagree with. There are much, MUCH better performing (faster, less memory use, etc) hashes than SHA1 if you don't need security. If you don't need security, and you don't need performance, and you don't have anything else available in libraries, then SHA1 is fine. But that's a pretty rare situation. SHA1 might not be a sign of insecurity, but it's often a sign of poorly thought out design.

adrian_b2 years ago

On modern Intel/AMD and ARM CPUs, which have SHA-1 instructions, SHA-1 is very fast, faster than many apparently simpler hashes.

There are no other so fast hashes with an output of at least 128 bits that you can find in available libraries and use immediately in your code.

The only alternative that is faster and long enough is to use an 128-bit polynomial hash like Poly1305 or the one used inside AES-GCM. Such polynomial hashes are available in various cryptographic libraries, but they are not packaged in a way that would allow them to be used directly in a hashing application, so you might have to extract the code from the library and add an interface to it, to make it usable.

Another alternative that has appeared recently is BLAKE3. BLAKE3 can be much faster than SHA-1, but only when it is computed in parallel with a large number of cores. If you do not want your hashing task to entirely take over your computer, SHA-1 remains faster.

There are many applications that need long hashes to make negligible the likelihood of a collision, e.g. for file deduplication. For such applications using SHA-1 is by far the least effort choice and there is no disadvantage in using it.

oconnor6632 years ago

> BLAKE3 can be much faster than SHA-1, but only when it is computed in parallel with a large number of cores.

SIMD optimizations are arguably more important than multithreading. Take a look at these SUPERCOP benchmarks on a recent Intel CPU: https://bench.cr.yp.to/results-hash.html#amd64-pascalinspiro.... Those are all single-threaded measurements, and the SHA-1 implementation there is hardware-accelerated, but BLAKE3 is still almost 4x faster. That speedup comes from AVX-512 vector operations, which serial hashes like SHA-1 can't take (as much) advantage of. Not every caller will want to use the AVX-512 implementation (see "downclocking"), but even with AVX2 BLAKE3 would still be faster.

Multithreading is a nice option to have, and of course it looks great in benchmarks if the input is long enough. But general-purpose library APIs like `blake3::hash()` in Rust don't do multithreading by default. They only use SIMD optimizations by default.

hyperman12 years ago

I'm not going to contradict you. This was just one random example, based on the git/SHA-1 story. To stay in this example: If someone is claiming SHA-1 is faster for them, I'd expect to document what other algorithms they tested, what the numbers were, why better things were unavailable. A future maintainer can check if the decision still makes sense based on the comment. Hence the huge comment from the post above.

The problem is, I have a few examples that make a lot more sense, but they can't be published here. They depend on local corporate circumstances and pretty obscure knowledge, and would be undecipherable for the HN crowd.

simion3142 years ago

You can link them to the manual, good languages documentation explain clear and when examples when is better to use GOTO. If there is an asshole in the team the official documentation would calm them down after they will be probably shocked that their cool language implemented this "evil" feature.

leephillips2 years ago

Julia’s approach is to not have GOTO as part of the language syntax, but to have @goto and @label macros. I feel that this strikes a good balance: it sort of discourages their routine use, but they’re there if you really need them.

gwbas1c2 years ago

In the past 20 years I've used a goto a few times, and then refactored it out once I was able to look at the problem with a clear head. I remember that, every time, I replaced the goto by breaking up a large method into smaller methods, and then replacing the goto with either return statements or logic that would essentially return.

Yes, a goto is part of our programmers' toolbox. But, now even I consider a "good goto" a sign that a larger method should be broken into smaller ones.

recursive2 years ago

Often this is true, but those smaller methods might need to share so much local state that the solution is worse than the problem.

Jtsummers2 years ago

This is where languages that permit nested functions come in handy. You can locally factor out new functions while preserving access to variables in the lexical scope without needing to pass them in as parameters or lift them into a higher level scope (object, class, global, file, etc.).

recursive2 years ago

That's a good solution. The next problem is that many code patterns, such as state machines, if naively converted from `goto` to `call()` will consume a lot of unnecessary stack space. This might not be a problem with a language/compiler that supports tail call optimization.

Jtsummers2 years ago

Right. Absent TCE and inline functions (the latter is now pretty much bog standard in every language in popular use, at least every compiled language), goto for state machines and similar uses can be much more efficient and also handle the concern of blowing up your stack. If you have TCE and inline functions, then mutual recursion is a perfectly efficient way to handle that kind of situation that is often (but not always) clearer than goto. And if you pair that with nested functions so that you can close over some common lexical scope, you eliminate the need to use global variables or to thread data through each function call (keeping your parameters to a minimum).

+1
gwbas1c2 years ago
munificent2 years ago

I tend to find that to be itself a code smell. But it happens. When it does, I like using local functions that just close over the same state.

wruza2 years ago

Most programmers and I believe even most teachers see technology not as an application, but as some sort of a blind religion. Shallow understanding only adds to this problem. I’ve seen enough of them saying “what, goto?” in disgust, but almost no one could explain why, apart from saying that it is a taboo and maybe remembering that it was in some book.

They need it, and it’s really good that it was “goto”. If it wasn’t, they would jump on another, more useful bogeyman, like eval.

And for those interested, the specifical use of goto that was considered harmful is its use in place of “for” and “[do-]while” flow control statements and explicit subroutines. Literally:

  loop: …;
  if (!cond) goto loop;

  a = 42; b = 13; goto sub;
  …
  sub: …
It was used as such by inertia, because these were times before invention/adoption of the structured code.

While you may not want to goto into inner scopes for obvious reasons, leaving two loops or goto-ing to a function cleanup section is perfectly okay and is much clearer than breaking the outer loop with a boolean or building ladders of ifs.

bigbillheck2 years ago

Structured programming has won so thoroughly and become so pervasive that it's hard nowadays to understand the context in which Dijkstra was writing.

'Don't use goto' doesn't mean 'never ever use a goto under any circumstance', it means 'consider using this thing called a "while" loop instead'.

munificent2 years ago

> 'Don't use goto' doesn't mean 'never ever use a goto under any circumstance',

This is simply not true. When Dijkstra said goto should be considered harmful, he literally meant that programming languages should not suppport it as a feature at all.

buescher2 years ago

For those that haven't seen Knuth's "Structured Programming with go to Statements" before: https://pic.plover.com/knuth-GOTO.pdf

Enjoy!

userbinator2 years ago

Another good use of goto is to implement state machines --- "goto state_x" is clearer and simpler than the loop-switch that people often use.

I can certainly say that over the years I've encountered code which was more difficult to understand and less efficient because either the author didn't want to use goto, or the language didn't have it. While it's true that you can always rewrite code to remove goto statements, it can definitely have a negative effect.

beej712 years ago

Oh, that's an interesting use I hadn't considered!

jll292 years ago

I like the presentation of the three nexted loops.

Maybe "break;" should become a shortform for a generalized "break(1);" (= break one level) if we want to avoid using goto.

Perhaps this can be implemented by combining a macro with setjmmp() and longjmp() from the standard C library, without requiring a compiler change.

wruza2 years ago

Yep, some goto uses were ostracized without ever providing the alternative. In other languages breaking numerous loops is done by naming the one you want to control:

  outer: for () {
    for () {
      for () {
        break outer;
OskarS2 years ago

This is much more confusing to me than just using a goto. From your example, it's not immediately clear at all to me if you're breaking out of outer, or if you're breaking TO outer. You can figure it out with this simple example, but it's way more confusing than it needs to be. And importantly: way more confusing than just using goto.

Given that break and continue are just gussied up gotos anyway, just put the label where you wanna go and goto it. I think it's one of the few perfectly valid uses goto.

wruza2 years ago

The source of confusion is my generic naming. Name it “files_loop: for …” and “break/continue files_loop” will be obvious to a reader. Also do not forget that a label is not a distinct point in C, it is a part of a “labelled statement” [1], so you do not break to it, you break/continue/goto it.

I think it's one of the few perfectly valid uses goto.

I fully agree, but for 99% of our religious peers it is an NSFW taboo.

[1] https://www.lysator.liu.se/c/ANSI-C-grammar-y.html#labeled-s...

Jtsummers2 years ago

There is no confusion if you're already familiar with the anonymous break:

  // find in a loop
  for (a : array) {
    if (a == target) {
      print("Found it");
      break;
    }
  }
This breaks the loop. A named break is no different except that it names the loop that will be broken out of:

  find:
  for (a : array) {
    if (a == target) {
      print("Found it");
      break find;
    }
  }
Exactly the same as the previous, but we've named the loop for some reason. If you can understand this, then the case of named breaks (or named continues) with multiple nested loops are comprehensible with some effort spent writing or reading illustrative examples like the above. Why would it do anything else? A break is a break, it terminates the loop. To re-enter that loop would be a continue, not a break.

> Given that break and continue are just gussied up gotos anyway, just put the label where you wanna go and goto it. I think it's one of the few perfectly valid uses goto.

The reason not to do this is that goto's can (as illustrated in the submitted article) lead to some erroneous behavior that is harder or impossible to achieve with more structured equivalents (like named breaks and continues). Like, your goto can jump past variable initializations and get you undefined behavior. Using the structured equivalent of:

  for (a : array) {
    if (a == target) {
      print ("Found it");
      goto found_it;
    }
  }
  found_it: ...
Removes those potential errors from the system. That, of course, doesn't mean that goto should be forbidden entirely (plenty of examples in this discussion of where it's useful), or that something like the preceding wouldn't be useful in some circumstance. But if we follow the idea that you present ("break and continue are just gussied up gotos anyway") to its conclusion, we'd be back to the unstructured code that was Fortran and its contemporaries, because why should we have if, if-else, while, for, function calls, etc. when they're all just gussied up goto anyways?
+1
rightbyte2 years ago
jkcxn2 years ago

Isn't it interesting that all of the examples in the original article can be replaced with a labeled break of arbitrary blocks? Even replacing the continue statement. Solving the problems of goto and keeping the behaviour. Not sure why more languages don't have this

beej712 years ago

The next version of C is likely going to have `break break` to break two levels. I'd have preferred `break label` or no change, but there we are.

Wildgoose2 years ago

I remember getting "sniffy" comments for using a GOTO in my final year project, (which was written in Pascal).

Basically, it was used to terminate the program without having to thread a return-value through very heavily nested code.

I haven't needed to use a GOTO since. But almost 35 years later, I still maintain it was the correct decision at the time: a single GOTO statement was the clearest and most efficient choice.

kazinator2 years ago

Goto is a useful precursor for writing a tail recursive solution.

For instance, say we have a state machine for recognizing that the last four button presses were 1234.

   int keypad() // returns 1 if correct key is entered, otherwise loops forever
   {
      keypad:
        switch (getkey()) {
        case 1:
          goto got_1;
        default:
          goto keypad;
        }

      got_1:
        switch (getkey()) {
        case 2:
          goto got_2;
        case 1:
          goto got_1;
        default:
          goto keypad;
        }

      got_2:
        switch (getkey()) {
        case 3:
          goto got_3;
        case 1:
          goto got_1;
        default:
          goto keypad;
        }

      got_3:
        switch (getkey()) {
        case 4:
          return 1;
        case 1:
          goto got_1;
        default:
          goto keypad;
        }
   }
Now, refactor the same structure into tail calls. Now it's suddenly beyond reproach.

   int got_1();
   int got_2();
   int got_3();

   int keypad()
   {
      switch (getkey()) {
      case 1:
        return got_1();
      default:
        return keypad();
      }
   }

   int got_1()
   {
     switch (getkey()) {
     case 1:
       return got_1();
     case 2:
       return got_2();
     default:
       return keypad();
     }
   }

   int got_2()
   {
      switch (getkey()) {
      case 3:
        return got_3();
      case 1:
        return got_1();
      default:
        return keypad();
      }
   }

   int got_3()
   {
      switch (getkey()) {
      case 4:
        return 1;
      case 1:
        return got_1();
      default:
        return keypad();
      }
   }
You can refactor any flow control graph based on goto, with local variables, into a network of tail calling functions. The tail call graph is isomorphic to the goto: it's just as complex and hard to understand. Yet it has the virtue of being beyond the reproach of computer science academia.
ladberg2 years ago

I think the better way to create a state machine would use another switch instead of the top-level goto like:

  int keypad()
  {
    int state = 0; // 0 = start, 1 = seen '1', 2 = seen '12', 3 = seen '123'
    while (true) {
      int key = getkey();
      switch (state) {
      case 0:
        switch (key) {
        case 1:
          state = 1;
        default:
          state = 0;
        }
        break;
      }
      case 1:
        switch (key) {
        case 1:
          state = 1;
        case 2:
          state = 2;
        default:
          state = 0;
        }
        break;
      }
      case 2:
        switch (key) {
        case 3:
          state = 3;
        case 1:
          state = 1;
        default:
          state = 0;
        }
        break;
      }
      case 3:
        switch (key) {
        case 4:
          return 1;
        case 1:
          state = 1;
        default:
          state = 0;
        }
        break;
      }
    }
  }
In my mind that reads more obviously as a state machine than a bunch of gotos and labels (and should function the same assuming I typed it correctly, I haven't run it anywhere). It's easy to see each state and how it flows into other states.
kazinator2 years ago

I don't see how that is better. Every one of your "state = N" assignment is just a goto in disguise, which requires the machinery to go back to the top and then jump somewhere again.

Someone looking at "state = 1" has to understand that state is a variable which controls the outer switch, and that 1 is refers to "case 1:" of that outer switch.

You have a proliferation of "case 1:" repetitions, so the reader has to follow the indentation carefully.

The goto is not a simple goto, either. When we assign "state = 1" it doesn't just mean to branch to the "case 1" node, but to go through the "key = getkey()" initialization.

The whole thing is less efficient than goto.

Specifically to C, you have more lines of code now. You remembered your break statements in the outer switch, but in the inner switches, you carelessly changed the gotos to assignments without adding the required break statements, so you have a bug in every inner switch. When you fix those, you will have some seven more lines of code.

Note how my original code has no variables at all, and neither does the transliteration into tail calls. You have two variables: key and state, plus you still have the same underlying goto graph structure.

The only saving grace of this arrangement is that it factors out the common action of calling getkey() into one place which is only important if we can't turn that common action into a tiny function call with just two or three arguments.

The property in my graph that getkey() is called in multiple places is a key property; it helps to farm off each block directly into a corresponding tail calling function in the mechanical transformation to the tail call graph.

I used to write all state machines this way, with labeled states, and a loop around a switch and input processing in one place at the top.

Quite recently, I did some ad hoc comaparative studies which convinced me I won't be doing that any more; I will be using disciplined goto graphs.

kazinator2 years ago

Also note that switch (state) { case STATE_LABEL: ... } is a form of computed goto. Every time we use switch in a C program, we are using computed goto.

The switch statement escapes the disparagement that is lobbed at goto for two reasons.

1. It doesn't have "goto" anywhere in its name. (Neither does an assembly language JMP instruction, yet it is still goto).

2. It has a limited scope: whereas goto can jump to any label anywhere in a function, a switch only branches within a statement. (But anywhere within that statement; into any level of nesting inside any sub-statement!)

If you wrap the bulk of your function with a giant switch inside a dispatch loop on a state variable, then, effectively, the (2) limited scope defense of switch no longer applies: you have perpetrated a bare-faced computed goto scoped over the bulk of the function body.

me_me_me2 years ago

The only acceptable use-case for goto I came across was for error handling and cleanup.

If there are multiple points of code failure that all require same cleanup procedure then one section at the bottom is acceptable and can actually keep the code cleaner.

Anything else looks like bad design that ends up producing hard to follow code.

mikevm2 years ago

Yes, this is pretty much how it is being used in C code. The other acceptable use-case is breaking out of nested loops.

There's also `setjmp()`/`longjmp()` but that's even more rarely used.

einpoklum2 years ago

longjmp() is the C-language version of going-to labels in other functions.

Its basic "use" is not having to unwind the stack; but moreover, it lets you avoid cleanup and resource de-allocation. Usually it doesn't make sense to do that (you get memory leaks), but if, say, your memory allocations happen in some kind of arena - you can just leave it in any junk state and then proceed to clear it altogether after your setjmp() instruction.

SAI_Peregrinus2 years ago

The most common use of setjmp/longjmp I've seen (in embedded development) is to have coroutines in C. The original use AFAIK was usually to implement exceptions of some variety. Neither use is that common these days.

zelos2 years ago

I remember working with two C teams where one did:

  rc = do_something();
  if (!rc) {
    rc = do_something_else();
  }
  ...
  do_cleanup();
  
and the other did:

    rc = do_something();
    if (rc) goto xout;
    rc = do_something_else();
    ...
  xout:
    do_cleanup();
I think the goto was clearer and harder to get wrong?
panax2 years ago

When you are forced to follow stupid rules like having only one exit point in a function then goto can be used a lot in place of that, but what are we supposed to do if you are not allowed to both use goto or have multiple returns in a function (in C) ?

zarzavat2 years ago

> what are we supposed to do if you are not allowed to both use goto or have multiple returns in a function (in C)

Move jobs.

But seriously this is a good point. goto is practically necessary to manage memory in C. Having only one return point is justified in functions that manage heap memory. Standard C has no RAII, defer, finally, or any other alternative but goto.

t435622 years ago

I find that using goto for cleanups makes many functions much much clearer and I think it cuts bugs.

Rather than writing the cleanup that is appropriate for each particular exit point you write one big cleanup at the end of the function that deals with everything.

It adds some potentially unnecessary if statements (to see if some pointer was allocated or not before trying to free it or to see if some file handle was opened before closing it). To me it has always seemed well worth it - shortening the code and greatly simplifying the chains of error-handling if statements.

marcodiego2 years ago

Actually "GOTO Considered Harmful" should be considered harmful. But goto should but not used by beginners, or else they get bad habits. Also, people should consider that there is a proposal for "break break" in C2X: http://www.open-std.org/jtc1/sc22/wg14/www/docs/n2859.pdf

vadfa2 years ago

Like any other tool in the arsenal of a programming language: when it best suits the task.

ravenstine2 years ago

Goto probably doesn't have much of a place in structured programming, but I think there are potential applications for it in more finite domains.

Though I never finished it, years ago I was working on a language specifically designed for voice activated devices, interactive fiction games, and so on. I wasn't happy writing Alexa skills using languages like JavaScript because such languages have more features and complexity than are ever used for a voice interface. So I worked on a lexer-parser for a language I invented that was very minimal.

These were the features of the language:

- No syntactical scoping. You have to be explicit about the "data set" you are using.

- Close to being more like an "assembly" language, every line being a command with parameters, but is human readable and declarative, and supports expressions.

- Control flow is done not only through the equivalent of a "goto", but something like "goto chapter" and "goto section".

- There is no syntactical concept of loops. Loops are effectively performed using gotos and conditions.

- Lines beginning with whitespace are interpreted as text or speech to be rendered.

For the purpose of what I wanted to do, this allowed me to focus more on the user experience and less on fitting my ideas into languages designed to support algorithms, OOP, and so on. The simplicity of using gotos meant less conceptual overhead; everything could be done with just if-else, goto, math operators, and negation.

I imagine goto may also be a good way to introduce young kids to the concept of programming, though I suppose that could backfire if they get too used to writing unstructured programs.

bsenftner2 years ago

Many C/C++ UI frameworks require allocating component parts, linking them together to create an operating widget, and then attaching callbacks and whatnot... The code creating a dialog/window/toolbar with multiple such widgets is a perfect case for using goto to implement a single code block of unwinding of the complex construction of that dialog/window/toolbar. Multiple times I've had long winded debates on why complex dynamic memory structure unrolling is safest as a single code block. The subtle win is the unexpected MASSIVE code shrinkage that occurs when a UI's codebase adopts goto for dynamic structure unrolling. I've seen applications that dragged become nimble if not perky after the code reduction from removing 2/3rds of the code that was nothing but repeats of partial structure unrolling due to an error at one spot, and a similar one a few lines down with a complete duplicate of the upper unrolling and a bit more added, and then another duplicate a new lines further, and again and again and again...

ahartmetz2 years ago

Maybe so in C, but in C++, stack-allocated container classes, smart pointers, scope guards(!), and (with Qt) the parent-child system can handle almost all cases. Binary size usually increases but lines of code are less or equal to a C-style solution.

It's called RAII, but the main point is really that destructors are automatically called on scope exit.

dang2 years ago

I put 2007 above because the book it's from seems to be from then.

Beej’s Guide to C Programming [pdf] - https://news.ycombinator.com/item?id=26911399 - April 2021 (171 comments)

Beej's Guide to C Programming - https://news.ycombinator.com/item?id=26100391 - Feb 2021 (1 comment)

Beej's Guide to C Programming (2007) - https://news.ycombinator.com/item?id=15198093 - Sept 2017 (79 comments)

Beej's Guide to C Programming - https://news.ycombinator.com/item?id=8295024 - Sept 2014 (1 comment)

ladyattis2 years ago

I think the issue is only relevant when considering the years when programming languages began to have more nuanced instructions. For Assembly, goto is an essential construct and you'll learn how to use it reliably as jump conditions. For C, it's a great way to get out of nested code that you couldn't avoid for the given problem. For more high level languages, continue and break are the 'goto' statements and they're used all over but they have built in restrictions which make them easy to use. Goto is just hard to master in use for some of us and it's okay to restrict ourselves to limited uses of it. I just don't know why it's become a mantra to say, "do not ever use goto or labels or jump conditions what so ever." It just seems odd to me.

dec0dedab0de2 years ago

For more high level languages, continue and break are the 'goto' statements...

I have never liked continue or break. It always felt like I messed something up if I found myself needing them.

ainar-g2 years ago

The site seems to be hugged to death, at least to me. Archive link: https://web.archive.org/web/20211021120233/https://beej.us/g....

continuational2 years ago

That error handling is extremely error prone. It may be the only option you have in C, but that doesn't make it any better.

einpoklum2 years ago

Can't say that I'm enthusiastic about goto advice, but - for a second there, I thought the domain was bejeez.us :-P

Grazester2 years ago

When bit banging on a slower microcontroller I have found goto has it's uses.

jugg1es2 years ago

The utility of goto is one of those things I've always understood but been too embarrassed to say out loud.

billpg2 years ago

My favorite use of goto is with C#'s "yield return".

The compiler converts a function with a yield command into a label. To resume a function from where it left off, the compiler adds code to test an added resumption-point variable and to goto the label for each resumption point.

fjfaase2 years ago

I have used the goto statement to implement enumerators classes in C++ using a state member and a switch statement with goto statements at the start of the method. Of course, also all local (loop) variables need to be replaced by (private) members. At each 'yield' location, you set the state variable, have a return statement, followed by a label.

It is also possible to do this in C with functions and structs. See for example: https://www.iwriteiam.nl/Ha_cmt.html

heavenlyblue2 years ago

All code that ends up being written as assembly will become a command (an opcode or a set of opcodes) and a label (the opcode's address).

DeathArrow2 years ago

I learned Basic in the 90s and there was no for, no while, no functions or procedures. Just ifs and goto. Its hard to structure a program just with goto and it's even harder to read it and understand the flow.

tyingq2 years ago

Perl has goto, but it also supports labels you can apply to loops, and call for redo, next and last. Where "next" is like "continue" and "last" is like "break".

drodil2 years ago

The most amazing goto is:

void main() { goto http; printf("Hello world!"); http://www.hello-world.com }

sltkr2 years ago

This won't actually compile, even if you format it correctly, because a label must be followed by a statement.

beej712 years ago

Oh good, I thought! Maybe there are some ideas here I could add to the book! :)

woliveirajr2 years ago

At StackOverflow you're downvoted heavily when you just typ got (don't event have time for the last "o").

Then Apple comes with the glorious "goto fail" and shows that real companies use goto, and the problem wasn't "goto" itself.

dleslie2 years ago

It seemed odd to me that their fail context didn't set 'err', but assumed it would be set by the origin of the jump. At least, I would have initialized 'err' to a failure value, so that any mistake that led it not to be set would result in a valid error state.

N0tAThr0wAway2 years ago

Goto is like a tool in your tool shed that may only be used once a year or less. It has an extremely narrow use-case, can be dangerous is used improperly, and may require more experience and training versus using your run of the mill pipe wrench.

I always hated the platitudes that people use when talking about gotos. Yes it definitely can be dangerous, yes it's probably best to refactor a specific area of your codebase to not use them, but every once in a while, it's the right tool for the right job.

m4r35n3572 years ago

You can use static local variables with goto to implement yield semantics in c, look it up!