Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

Very nice, using the goto trick to perform cleanups always felt dirty to me.


I use a few strategies instead of goto:

(1) For simpler cases, wrap in do {} while (0) and break from the loop

(2) For multiple cleanups, use same technique combined with checks to see if the cleanup is required. E.g. if (f != null) fclose (f)

(3) put the rest of the stuff in another function so that the exit code must run on the way out.

In 35 years of coding C/C++, I've literally never resorted to goto. While convenient, this new defer command looks like the kind of accidental complexity that templates brought to C++. That is, it provides a simple feature meant to be solve simple problems in a simple way that accidentally allows architecture astronauts the ability to build elaborate footguns.


`the do {} while (0)` block with breaks does exactly what goto does but it is so much more hacky, less flexible and harder to follow IMHO.


35 years of coding C don't amount to much if you ignore best practices. Look at the Linux kernel to learn how gotos can be used to make the code safer and improve clarity.

There's probably something wrong if a substantial project in C does NOT use gotos.


Would any particular piece of the kernel stand out to you in this manner? Last time I looked I didn't think too much about it being used.


How do you handle breaking out of a nested for loop without goto?

    for (…) {
        for (…) {
            if (…) {
                …
                goto found;
            }
        }
    }
    found:
This is straightforward with goto and may even be vectorizable. I guess you could move the loop to a separate function or add additional flags to each loop, but neither of these seems like an improvement.


Obviously first two strats don't work, so I would use the third.


I really like the goto trick. It makes functions readable, versus the mess you have without it.


Indeed!


gotos can be used without shame. Dijkstra was wrong (in this rare case).

defer is cleaner, though.


Dijkstra was not wrong. Modern programmers are wrong in thinking that the goto that they use is what Dijkstra was talking about, merely because of the fact it happens to be called the same thing. I mean, I get how that can happen, no sarcasm, but the goto Dijkstra was talking about and what is in a modern language is not the same. https://jerf.org/iri/post/2024/goto/

The goto Dijkstra is talking about is dead. It lives only in assembler. Even BASIC doesn't have it anymore in any modern variant. Modern programmers do not need to live in fear of modern goto because of how awful it was literally 50 years ago.


The interpretation of Dijkstra's sentiment in your blog post is plain wrong.

His paper [1] clearly talks about goto semantics that are still present in modern languages and not just unrestricted jmp instructions (that may take you from one function into the middle of another or some such). I'd urge everyone to give it a skim, it's very short and on point.

[1] https://homepages.cwi.nl/~storm/teaching/reader/Dijkstra68.p...


Well, there you get that I don't believe in letting certain people own ideas and then stick to them as if they were given revelation from on high about how things should work. The distinctive thing about goto that breaks structured programming, to the point that functions as we think of them today can't even exist in such an environment, is the ability to jump arbitrarily.

I'm way less worried about uses of goto that are rigidly confined within some structured programming scope. As long as they stay confined to specific functions, they are literally orders of magnitude less consequential than the arbitrary goto, and being an engineer rather than an academic, I take note of such things.

I don't ask Alan Kay about the exact right way to do OO, I don't ask Fielding about the exact right way to do REST interfaces, and I don't necessarily sit here and worry about every last detail of what Dijkstra felt about structured programming. He may be smarter than me, but I've written a lot more code in structured paradigms than he ever did. (This is not a special claim to me; you almost certainly have too. You should not ignore your own experiences.)


Your analysis is wrong. Dijkstra was a big proponent of structured programming, and the fundamental thesis of his argument is that the regular control flow structures we're used to--if statements, loops, etc.--all represent a tree-based data structure. In essence, the core argument is that structured programming allows you to mentally replace large blocks of code with black boxes whose exact meanings may not be important.

The problem with GOTO, to Dijkstra, is that it violates that principle. A block can arbitrarily go somewhere else--in the same function, in a different function (which doesn't exist so much anymore)--and that makes it hard to reason about. Banning GOTO means you get the fully structured program that he needs.

(It's also worth remembering here that Dijkstra was writing in an era where describing algorithms via flowcharts was common place, and the use of if statements or loops was far from universal. In essence, this makes a lot of analysis of his letter difficult, because modern programmers just aren't exposed to the kind of code that Dijkstra was complaining about.)

Since that letter, modern programming has embraced the basic structured programming model--we think of code almost exclusively of if statements and loops. And, in many language, goto exists only in extremely restricted forms (break, continue, and return as anything other than the last statement of a function). It should be noted that Dijkstra's argument actually carries through to railing against the modern versions of break et al, but the general program of structured programming has accepted that "early return" is an acceptable deviation from the strictly-single-entry-single-exit that is desired that doesn't produce undue cognitive overhead. Even where mind-numbing goto exists today (e.g., C), it's similarly largely used in ways that are similar to "early return"-like concepts, not the flowchart-transcribed-to-code-with-goto-as-sole-control-flow style that Dijkstra is talking about.

And, personally, when I work with assembly or LLVM IR (which is really just portable assembly), I find that the number one thing I want to look at a very large listing is just something that converts all the conditional/unconditional jumps into if statements and loops. That's really the main useful thing I want from a decompiler; everything else as often as not just turns out to be more annoying to work with than the original assembly.


I struggle with how you claim my "analysis is wrong", and then basically reiterate my entire point. I know it's not that badly written; other people got it just fine and complain about what it actually does say.

The modern goto is not the one he wrote about. It is tamed and fits into the structured programming paradigm. Thus, ranting about goto as if it is still the 1960s is a pointless waste of time. Moreover, even if it does let you occasionally violate structured programming in the highly restricted function... so what? Wrecking one function is no big deal, and generally used when it is desirable that a given function not be structured programming. Structured programming, as nice as it is, is not the only useful paradigm. In particular state machines and goto go together very nicely, where the "state machine" provides the binding paradigm for the function rather than structured programming. It is perhaps arguably the distinctive use case that it lives on for in modern languages.


> The modern goto is not the one he wrote about. It is tamed and fits into the structured programming paradigm.

No, it doesn't, not the goto of C or C++ (which tames it a smidge because it has to). That's the disconnect you have. It's not fine just because you can't go too crazy and smash other functions with it anymore. You can still go crazy and jump into the middle of scopes with uninitialized variables. You can still write irreducible loops with it, which I would argue ought to be grounds for the compiler to rm -rf your code for you.

There are tame versions of goto--we call them break, continue, and return. And when the C committee discussed adding labeled break, and people asked why it was necessary because it's just another flavor of goto, I made some quite voluminous defense of labeled break because it was a tame goto, and taming it adds more possibility.

And yes, the tame versions of goto violate Dijkstra's vision. But I also don't think that Dijkstra's vision is some sacrosanct thing that must be defended to the hilt--the tame versions are useful, and you still get most of the benefits of the vision if you have them.

In summary:

a) when Dijkstra was complaining about goto, he would have included the things we call break, continue, and early return as part of that complaint structure.

b) with the benefit of decades of experience, we can conclude that Dijkstra was only partially right, and there are tame goto-like constructs that can exist

c) the version of goto present today in C is still too untamed, and so Dijkstra's injunction against goto can apply to some uses of it (although, I will note, most actual uses of it are not something that would fall in that category.)

d) your analysis, by implying that it's only the cross-function insanity he was complaining about, is wrong in that implication.


"You can still go crazy and jump into the middle of scopes with uninitialized variables."

It is difficult when speaking across languages, but in many cases, no, you can't.

https://go.dev/play/p/v8vljT91Rkr

C isn't a modern language by this standard, and to the extent that C++ maintains compatibility with it (smoothing over a lot of details of what that means), neither is it. Modern languages with goto do not generally let you skip into blocks or jump over initializations (depending on the degree to which it cares about them).

The more modern the language, generally the more thoroughly tamed the goto is.


It doesn't even need to be a modern language to protect against that:

  BEGIN
    INT x := 1;
    print(("x is", x, newline));

    GOTO later;

    INT y := 2;

   later:

    print(("y is", y, newline))
  END
for which we have:

  $ a68g goto.a68 
  x is         +1
  11           print(("y is", y, newline))
                            1           
  a68g: runtime error: 1: attempt to use an uninitialised REF INT value (detected in [] "SIMPLOUT" collateral-clause starting at "(" in this line).
Although admittedly it is a runtime error.

However if y is changed to 'INT y = x + 2;', essentially a "constant", then there is no runtime error:

  $ a68g goto.a68 
  x is         +1
  y is         +0


"when I see modern code that uses goto, I actually find that to be a marker that it was probably written by highly skilled programmers. "

He should have said "correct code", not "modern code" because the times I remember seeing goto the code was horribly incorrect and unclear.

(With break and continue, someone has to be doing something extra funky to need goto. And even those were trigger signs to me, as often they were added as Hail Mary's to try to make something work)

{I typically reviewed for clarity, correctness, and consistency. In that order}


Or the tl;dr in modern parlance Dijkstra was railing against the evils of setjmp.


Sometimes setjmp is useful and I had occasionally used it, but usually it is not needed. There is certain considerations you must make in order to be careful when you are using setjmp though.

(Free Hero Mesh uses setjmp in the execute_turn function. This function will call several other functions some of which are recursive, and sometimes an error occurs or WinLevel or LoseLevel occurs (even though these aren't errors), in which case it will have to return immediately. I did try to ensure that this use will not result in memory leaks or other problems; e.g. v_set_popup allocates a string and will not call longjmp while the string is allocated, until it has been assigned to a global variable (in which case the cleanup functions will handle this). Furthermore, the error messages are always static, so it is not necessary to handle the memory management of that either.)


No, even setjmp/longjmp are not as powerful or dangerous. The issue is not the locality of the jump, but the lack of any meaningful code structure enforced by the language. Using setjmp and longjmp properly still saves and restores context. You still have a function call stack, a convention for saving and restoring registers, locally scoped variables, etc. Though, using setjmp/longjmp improperly on some platforms might come close, since you're well into undefined behavior territory.

Parent is correct that this doesn't really exist outside of assembly language anymore. There is no modern analogue, because Dijkstra's critique was so successful.


Lua uses it for error handling. It is really hard to understand the lua code. :/




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

Search: