Hacker Newsnew | past | comments | ask | show | jobs | submitlogin
Goto Is Not a Horror (jerf.org)
51 points by todsacerdoti on March 21, 2024 | hide | past | favorite | 50 comments


Linus Torvalds on Gotos[1]:

> I think goto's are fine, and they are often more readable than large amounts of indentation. That's _especially_ true if the code flow isn't actually naturally indented (in this case it is, so I don't think using goto is in any way _clearer_ than not, but in general goto's can be quite good for readability).

[1]: https://koblents.com/Ches/Links/Month-Mar-2013/20-Using-Goto...


The world is moving away from imperative programming altogether, towards functional programming. Pretty much anything that can be done with control flow can be done with data flow instead, leaving fewer opportunities for error. Data flow is a higher level of abstraction that's cleaner and easier to reason about.

If anyone reading this feels like trying something new, I encourage you to learn functional programming instead of finding creative-but-safe ways to use goto statements.


> The world is moving away from imperative programming altogether, towards functional programming.

I think this opinion is a bit too coarse to describe current trends.

Certainly, current best practices recommend things like std::ranges, Streams, or LINQ instead of old-school for-i (or even for-each) loops, with good reason: the pipeline and map-reduce paradigms are very useful to simplify and shorten code especially when the same operation is applied throughout a data structure. This sort of operation also tends to be embarrassingly parallel, easily lending itself to performance boosts with SIMD and vectorisation.

However, pure, stateless functional programming is useful only in limited contexts, and implementations tend to rely on either a GC runtime, or resorting to copying and overwriting data structures to maintain correctness and safety at the (sometimes very large) expense of performance and efficiency.

Purely stateless and functional programming is extremely hard to use in overwhelmingly stateful (read: video games) applications without things slowing down to unusable levels. Update an ECS tag? Write the entire state (which can be megabytes large) all over again—no in-place updates, after all.

I feel most (non-functional) programming languages are adopting FP paradigms, but will maintain their imperative core by replacing old, leaky, and error-prone abstractions with data-flow paradigms (like the for-i -> map-reduce I mentioned above).


>The world is moving away from imperative programming altogether, towards functional programming.

I think functional programming has it's advantages and it's worth learning. But no, imperative programming is still the most popular will remain so for some time.


I think the more correct statement would be: "The world is moving away from imperative programming altogether, towards declarative programming".

IMO functional programming is just a special kind of imperative programming. I think real distinction is between "imperative/functional", where you describe how to do thing, and "declarative" where you don't care about the hows, but you specify what is the end result you want to see. For example: html (declarative) "I WANT A RECTANGLE AT THIS POSITION AND SIZE": <div style="top, left, height, width">, while imperative, you exactly tell the how, like <for i> then <for j> draw a pixel at that position i,j. Making drawing a box as functional does not make too much sense, because that is a task, which does not benefit from the functional paradigm, but it does helps a lot with cardinality and null checking problems when dealing with other kind of problems.

I think it is already changing into this. Basically every single framework is one kind of declarative wrapper over an imperative language, or you can say every single function library makes the way how you work a little bit more declarative (like in the box drawing demo, you can just use function to write a box. I think in 20-30 years, in a very twisted way programming will be completely declarative by you telling an AI to write a code, first at a very low level of abstraction, but in time more and more complex ways. We'll see


I was writing a parser for a reasonable subset of HTML5 (a full parser would actually need to include JavaScript parsing as well) and there are certain states where the same character must be reprocessed under a different parser state. [1]

I wrote my parser by iterating character by character (foreach) and when reaching those states that need reprocessing I found it nicer to jump to a goto label at the start of the foreach, after the state variable was updated.

If anyone is aware of a purely functional HTML5 compliant parser let me know, as I'd love to steal some ideas.

[1] https://www.w3.org/TR/2011/WD-html5-20110113/tokenization.ht... ctrl-f reconsume



That is a HTML generator library.

And as I've said, for HTML5 compliance you need to also parse JavaScript, see second sentence here https://dev.w3.org/html5/spec-LC/parsing.html#overview-of-th...

A good first smoke-test for HTML5 supporting parsers is the following html fragment

   <input disabled>
XML-like parsers (masquerading as having "HTML5 support") will fail if tags aren't closed or self-closing, and if there are attributes without an explicit value.


I found elixir a fun inroad to functional programming.


>"The world is moving away from imperative programming altogether, towards functional programming."

Well, no. Rational world use what makes sense for particular situation. Not silver bullet du jour that suddenly solves everyone's problem (not).


> 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.

cough `longjmp()` cough. But yeah, it's not called goto...

I've never understood the hate goto gets, there are perfectly fine use cases for goto in most languages which have a goto statement. And even if one would expect otherwise most code I've seen using goto uses it in a sane way or at least intended way (that a modern language like Go can't break out of nested loops without using a label is just plain silly imho). In C there are a thousand ways you can shoot yourself in the foot, goto is nowhere near the top list.


Early on in my career as a summer intern I had to port a piece of industrial equipment code that someone new to programming had written on an Apple II over to an IBM PC infrastructure (since Apples were being discontinued.) Some features were supposed to be added to the code as part of the port.

The code was littered with gotos. Let’s say it was a 2000 line program with 100 gotos and no comments. It was very hard to follow the code flow. The “structured programming”/procedural style of naming each bit of (say) 25-50 lines of code according to its purpose and providing clear entrance and exit points was a huge advance in making code easier to work with by anyone other than its original author. Huge.

All variables were also global in those days, there were not locally scoped variables. This made gotos even worse. Reasoning about the state of a program combining global variables and lots of gotos could get quite complex for someone new working on an unfamiliar code base.

People don’t misuse goto nowdays because teachers, books, articles don’t teach it as a core primitive for flow control, due to shame. So now it’s kind of an advanced feature and not a problem. But back in the day, it made a real mess in the hands of a certain percentage of coders who invariably existed and lazily kept reusing existing lines of code, one spaghetti-mess of a goto at a time. If you’ve ever worked with someone else’s extremely/overly abstracted “DRY” code it was like that but 10x worse.


I am far from saying that code like this doesn't and didn't exist, it does and it did a lot more back in the day, absolutely. But having read my fair share of spaghetti code written by bad programmers I can also say that you don't need gotos to make code bad and unreadable. If someone doesn't know know how to properly structure code they will manage just fine without a single goto and beyond some point it doesn't matter anymore if you add goto to the recipe since it's that bad already.

You mention shame and teaching and that's a valid point historically, I was told and taught the same things and wouldn't use goto at all until I figured out (myself) that certain kinds of cleanups and control flows become much less spaghetti and more readable by using goto. Instead of shaming and teaching a silly dogma "just because" people could have taught how to use it properly in those few idiomatic use cases. It's not rocket science. It's a shame (pun slightly intended) that some code is actually less readable than it could be just because someone was taught to never use goto because it was the easier explanation. It's like lying to children because you think they can't handle an honest explanation, in almost all cases it will bite you in the ass at some point.


I think the hate for goto and the love of recursion have their origins with the ideas of the early 1970's when people like Dijkstra were obsessed with generating formal proofs from code. At the time they knew how to reason about recursion but not loops. And didn't know how to transform code with goto's.

They thought that if you banned things like loops, gotos multiple returns, etc you could produce a language suitable for 'real mathematics'. Turns out this is mostly garbage. But 50 years later it gets taught and people still believe it. People think recursion is this super duper thing when it's not. And people think multiple returns, goto, and continue statements are bad when they are fine.


While such people may exist then and now, Dijkstra was not one of them. He was not complaining about the structured control flow of modern languages (yes, C is pretty modern relative to this concern) but unstructured jumps all over the place. The issue is not multiple return but multiple entry points and multiple exit destinations. Raw machine code and assembly had no notion of procedures or functions and so many early "high-level languages" didn't either. You could jump into the same chunk of code at different places and then jump back out of it to yet more random places. This was even seen as a feature by some because it enabled more aggressive code reuse. But the code quickly became unreadable and following the control flow through the code could become nigh-on impossible (the original "spaghetti code" which really could go from anywhere to anywhere at just about any time). This led to many subtle bugs which also in practice made the pieces of code less reusable since weaving through the code so much often picked up side-effects from its many different purposes. The use of structured procedures with a single entry point and well defined exit behavior (which we now call "return" because you exit back to the same place you came from every time) is what Dijkstra wanted, and he was right. It has become so well accepted that people don't even understand what he was complaining about because modern languages don't allow it.


That makes a lot of sense, thanks for your reply!


The arguments I've heard are basically increased cognitive load and the ability to make spaghetti logic. There may also be some concern, depending on the language, that problems are more likely if you can arbitrarily exit a scope/context without cleanup steps you might be counting on.

IMHO, I believe the reasoning behind "exceptions are bad" comes from a similar place.


Finally someone who gets it. The echo chambers that exists among programmers today is painful. And it is especially bad on SO when you ask a question and someone just comments one of the following without going into depth as to the why:

"Don't use goto it is bad"

"Don't use reflection it is slow"

"Don't do that it is an anti-pattern"

"Don't use X it is considered bad practice"

I understand there are rules of thumb when programming, but they are not set in stone. There is a reason the language designers haven't removed or deprecated features.


i was talking to a friend about this the other day, it makes working with other people infuriating, doubly so if you work in the microsoft .net world.

people don't just avoid but they actively oppose thinking for themselves. if it isn't a "principle" or a "rule" then it's wrong, and if there isn't a popular blog post granting you permission to do something, it's probably a bad idea.

you can take an idea, walk them though it, lay out the pros and cons, explain how it helps to solve a problem, even create a working prototype and show it to them, and the entire microsoft-brained team will watch it work then start groaning "hmmmmm, ehhhhhhhh, uhhhhhhh" in unison before deciding that they'd rather not solve the problem at all than do something "scary".


Some of us use goto in C# because it has none of the footguns, even if it annoys some less open-minded colleagues :)

I do feel you regarding the culture (arguably, it’s a thing with many modern languages sadly) which is why I’m taking a year-long vacation to work on OSS projects - the language is beautiful but the atmosphere was insufferable in the last team. There are better places fortunately but one needs to recharge.


I am considering doing the same but knowing myself I'll probably not do it. I salute you.


I wish you weren't as spot-on as you are. 100% this :(


Dijkstra was interested in defining program semantics using predicate transformers. It’s rather difficult to define useful semantics that way when unrestricted goto is available, so he preferred to avoid the difficulties it creates. It’s important to understand though that his objection was not stylistic, but mathematical. He was a trained mathematician who through circumstance found himself programming computers and he always had that perspective.

Incidentally, predicate transformer semantics are used to reason about imperative programs in an expressly functional mathematical way. They really are quite interesting. They’re also getting a bit more exposure thanks to modern theorem provers like Z3 and languages like Dafny.


Truly, the man with ideas that are both mathematically insightful and one of the best practical ideas in the history of programming is a rare breed. I wish I could have met him.

I regretfully do not know my history will enough to know if he invented structured programming as we know it, but his advocacy certainly played a large role in its acceptance.


In the vein of goto, premature optimization, clean code, testing, etc... I think most programming dogmas -- sans of K.I.S.S/write the least amount of code that fulfills all the requirements -- is incorrect, in that it shouldn't be applied haphazardly, without first giving some thought about your unique circumstances.

I've found it better to develop an intuition for these sorts of things, by making mistakes (or seeing those of others), and going from there. In the case of goto, as the author mentions, it really isn't implemented in modern languages the way it used to be (i.e. goto anywhere in the program). But sometimes it is necessary (e.g. early exit a try-catch block, jumping to an error-handling & cleanup block in gruesome circumstances, breaking out of deeply nested loops (never having deeply nested loops is another dogma I don't agree with), in state machines, etc.).


Goto is the means of creating new flow control mechanisms. C doesn't have labelled break, so you use goto. C doesn't have RAII or defer so you use a goto ladder.


I use goto occasionally to hop out of nested loops


I often use it even in plain loops, helps me avoid nesting ( https://github.com/ashvardanian/SimSIMD/blob/3e51bacb1b74a7e... ), but for most programmers it probably only associates with `goto cleanup;` used to destroy variables allocated earlier in the scope in case of an error.


I've often wondered what the goto haters think about Scheme continuations.


The sample flow-matic program here is a good example if anyone wants to see a true unstructured program. https://en.m.wikipedia.org/wiki/FLOW-MATIC


Dupe: https://news.ycombinator.com/item?id=39753098

(No comments there, either.)


I’m old and grumpy but in my experience ya can’t write a performant operating system without Goto


Can't believe no one has posted this yet (relevant XKCD):

https://www.explainxkcd.com/wiki/index.php/292:_goto


> You can not goto ... , in the middle of an if statement, in the middle of a for loop.

Uh, yes?

  goto foo;

  for (;;) {
    if (0)
      foo:;
  }


That is eliding quite a bit of the actual quote, “from one function into another,”.

The goto being spoken about here is the unconstrained type that allows for both modification of jump location during run time and jumping in the whole address space of either program or system.


The goto being written about is something that cannot jump even between ifs and loops, clearly. I.e. not the C goto, but perhaps something severely restricted, like the Common Lisp tagbody/go.


The inability to jump between functions is the main point. It is a sequence where I meant one goto doing all of them at once, not a choice list.

Also there are a few quirks here and there in a few languages, but it would break the flow to stop and enumerate the ones I know, plus I'd still miss some.

C is quite unruly, as it dates back to when this was a live debate, and this is one of the ways it being "portable assembly" is a reasonable statement. Duff's device is not possible in almost any other language. ISTR one other, but I'm not sure what it was... I may even be thinking about this: https://www.perlmonks.org/?node_id=388976 and that's 2004, not sure if perl can do that anymore.


I do not believe the article is correct, then. Dijkstra's goto paper squarely applies to the C local goto. The subject of the paper is not specifically machine language branch instructions that can go anywhere, or something like the global goto in line numbered BASIC (a language he did revile, claiming that it damages the brain).

I'm looking at the paper again. It clearly states that goto should be abolished from higher level languages.

Note that Dijkstra also lumps the repetition statement (while B repeat A / repeat A until B) with goto, and then carefully argues why that is an acceptable use of goto, namely that it is susceptible to analysis by induction.

I can't see how I can reach a conclusion other than that, yes, C has a goto of a type that Dijkstra considered harmful.

Some have argued that forward goto in C is easily analyzable, since it doesn't create loops. There are coding standards that permit forward goto, but not backward goto. What Dijkstra would have thought about the inclusion of a forward-restricted goto in a HLL is anyone's guess.

A C forward goto could jump from the middle of one loop into the middle of another one, though. Simply being forward isn't enough to ensure a certain property of the function's control flow graph. This is written about extensively in the Dragon Book.


Lots of words to say "goto can be good" with nary an example of a good use of goto. Hard to judge the point without a concrete example.


Good uses of `goto` generally fall into the following categories:

* C-specific compiler-independent cleanup. This is completely unnecessary if your language supports proper destructors. Unfortunately many still-used and newly-invented languages are completely insufficient here. GNU C has `__attribute__((cleanup))` but it's kind of clunky.

* `continue` or `break` for an outer loop. One of the few things Java did correctly was supporting this directly, with names. A few languages support break/continue by number which is error-prone (fortunately they don't make the same mistake C did in adding `break`-skew for `switch, though unfortunately that usually means they also give up on efficient multi-way dispatch)

* nontrivial variants of `fallthrough` for a `switch`. This is generally an indicator that you should refactor out an `inline` function, but sometimes that makes the code significantly uglier since most languages don't support local-promoted-dynamic variables.

* computed `goto` in interpreter loops, state machines, etc. There is no viable replacement for this, though if your compiler is capable of inlining through an array of function pointers that helps (last I checked, GCC and Clang both failed very badly).

* coroutine implementations. Efficient coroutines have to be built-in to the language anyway (so they can optimize the existence of locals), but languages that support coroutines tend to abandon sanity all ye that enter here.


Re write:

static int __init init_nfs_fs(void)

line 2046 without goto. See which version you prefer? I don't see goto use as mandatory.

https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/lin...


That's trivial in any language with destructors.


yeah, until your destructor throws...


Ignoring for a moment that kernels generally disable destructors, and that "failed to clean up" is likely to leave your program in an unrecoverable state even without multiple exceptions being involved (think about what an exception means ... "failed to write to stderr" should not be an exception, and you should be doing little enough allocation during destruction that your dtor can manage even if out of memory) ...

Destructors throwing isn't broken, it just requires explicit care from the programmer to figure out which option to use in case of conflicting exceptions:

* If the existing unwind is a non-exception-unwind, there is no conflict. Exceptions thrown from a destructor just work™ and change it to an exception-unwind. Since C++11 you have to explicitly mark the dtor as `noexcept(false)` to indicate that you think you know what you are doing.

* If you're already winding due to an exception and want to preserve it, you can check for it at the start of the dtor, and wrap the dtor body in a try-catch which conditionally rethrows if there wasn't already one.

* If you're already unwinding due to an exception and want to ignore it in favor of the new exception ... doing this in C++ is pretty ugly but it's certainly possible. In order to use bare `throw` for the no-additional-exception case, you have to replace all stack variables with `option`-like wrappers so you can manually dispose them during `catch`. Other solutions exist if you don't care about correctly using bare `throw`.

* Or, you can do the safe thing and say "simultaneously unhandled exceptions means the programmer likely never considered how to handle this; give up"

Note that most languages that use `try-finally` have even bigger problems to deal with, since they have to worry about `return` as well as `throw`. Bugs and inconsistencies are frequent here.


init_nfs_fs() use of multiple gotos starting to look trivial in comparison to that, huh?[1]

[1] or trivially replacing with so many nested if statements.


`init_nfs_fs` doesn't "throw" from any of its destructors though - most destructors don't even want to throw. Note that all the functions called between the `out` labels don't have (or at least don't do anything with) a returned error value.


As mentioned in the "Go To Statement Considered Harmful" paper, goto is good as long as it is bridled. The goto horror stems from unbridled gotos.

Pretty much every language created in the past 50-some-odd years, encompassing effectively every language you will actually use, enforce bridled gotos, so for all practical purposes there is no bad use of goto.


"goto is not an evil code devouring monster" != "goto is good". It's rarely useful. It would be hard to truly remove from most languages that support it.

But there's a reason even in languages that have it, you use it rarely. Structured programming is really good.


"goto" is the only way in Lua to skip to the end of the loop without nesting conditionals, because they still don't have "continue".


Please see my examples below.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: