GOTO (2000)
azillionmonkeys.com
azillionmonkeys.com
The goto of Dijkstra jumps to somewhere else in the program. You're somewhere in the call stack and decide to scribble on some registers and jump to some arbitrary location. Like longjmp with the restrictions taken off. As in across function boundaries. When you're writing assembly that works exactly as it it sounds like it would.
The goto of C jumps to somewhere else in the same function with a bunch of compiler warnings if you manage to bypass an initialisation. You can make control flow more complicated within a single function, sure.
Goto in C is not a significant problem. Jumping between different functions in assembly is however legitimately confusing.
This is something of a pet annoyance because various languages or coding standards decide to outlaw the friendly easy goto as it has such a scary reputation. This makes some algorithms much more complicated to express and others inherently slower.
Dijkstra's paper is not strictly focused on a global, machine-language goto that can branch anywhere in the address space.
In fact, Dikjstra mention that he would also like to banish statement like while B repeat A and repeat B until A, because we have recursion, which makes them unnecessary. He lets them slide only for reasons of "realism" and because such loops are analyzable with inductive techniques.
I think the distinction between goto as rendered in C and goto as rendered in machine code is significant. It's the same argument in favour of structured programming in both cases. The gap between structured programming and local goto within a function is smaller than in the assembly case.
It is only my speculation, but it seems very likely that Dijkstra had the Algol 60 and Algol 68 go to statements in mind. As in, an example high level language from which to banish the go to statement would have been whatever successor is planned for Algol 68.
Every time I've seen this algorithm presented, it's used recursion, because that's a very natural way to express backtracking. But Knuth doesn't, he basically never uses recursion in his TAoCP algorithms, he expresses his algorithms in a "flow chart" style, not assuming things like stacks or subroutines (in fact, one of the exercises in the chapter is to convert Algorithm B to a recursive version, which he then quickly dismisses in the answer to the exercise as "elegant, and works find for small problems" but fundamentally not very high performance and not really clearer than the non-recursive version).
So, I set down to implement Algorithm B in a generic way and used it to solve n-queens. The way Knuth has written it, it can be straightforwardly translated using `goto`s (it has lines like "if so-and-so, go to step B4, else B3"), but being the modern, structured programmer that I am, i tried to convert it to structured programming using loops and if statements.
It was doable, but when benchmarking, my versions were always slightly slower than Knuth's original "goto" formulation, and honestly I don't think they were more readable either. You had to add a bunch of variables to keep some state around that was implicit in Knuth's version. The recursive version was definitely more readable, but Knuth was of course correct, you pay a performance penalty for that readability.
It was a real eye-opener. In the hands of a master, "goto"s really do have a place, and when well-deployed, they can sometimes simply be superior to "structured" code using loops, ifs and recursion.
There was an argument like this on the comp.lang.c newsgroup a few years ago. Someone proposed a coding problem: I think it was writing a program that removes comments from C source, but preserves preprocessing directives and all else.
So various solutions were posted.
I wrote one that used a state machine based on goto; no control structures at all beyond assigning to variables and if/goto.
That original poster then revealed his solution. It consisted of mutually tail calling functions.
He specifically criticized the goto graph solution.
I claimed they are basically the same, and to bolster my argument, I wrote a trivial simple text filter which converted that tail calling solution to a goto graph.
Tail calling is not a higher level feature compared to goto. It is only syntactic sugar which leaves the program with the same organization.
In some ways, the goto program is easier to understand. Why? Because:
1. The variables of the graph are all declared together in one place. Say you have a local variable x. All goto graph blocks that refer to x are referring to the same thing. In the tail program, every function that does something with x has to declare it for itself as its own parameter. It's not obvious that those x's are supposed to be the same entity.
2. Typically, functional languages, like C, do not have named parameters. The state is passed among the tail-calling function via strictly positional arguments. Not all functions have all the state variables, so a given state variable could be argument 3 in one function or argument 1 in another function.
In the goto program it's very clear. If you have
bar:
x = s + 1;
goto foo;
we know: oh okay, this block updates the x state from s, and goes to foo. In the functional version: bar(s) -> foo(s + 1)
we don't know what s + 1 is doing. We have to read foo: foo(x) -> ...
ok, we know that s + 1 will be called x in foo. But is that consistent? Is it the same as every other x we see everywhere in the tail call graph?We easily see that consistency in the goto graph: there is a single x variable in the scope of the entire graph; anything that assigns to x is preparing a new value for that x.
> 1. The variables of the graph are all declared together in one place. [...]
> 2. Typically, functional languages, like C, do not have named parameters. [...]
I have little experience in C, but probably I would try to use a struct for the machine state.
Even with no tail calls I think I would try to write the machine as mostly block of
bar:
step_bar(&state);
goto foo;
From my optic the benefit of tail calls is the ability to mix flow control and machine steps with function abstractions. void step_bar(state *state)
{
state.x = state.s + 1
return step_foo(state)
}
This is where the single dispatch OOP with implicit class-scoped "self" reduces noise: void state::step_bar()
{
x = s + 1;
return step_foo();
}
You have the tail calls, all variables in one place: x is always the same x.
You know which variables are being set in each state transition; no need to look through parameter positions in function definitions.I think I may even have used it in Go once, but I can't remember what for. Go's defer keyword obviates the above use of goto, and labeled breaks almost any other case I can think of.
static regex_compare_t regex_canonical_equivalent_with_structures(
regex_cache_t *cache, stringtable_index_t x, stringtable_index_t y,
intstack_t *stack_arg, intmap_t *map_arg) {
// fairly complicated code using those structures
}
regex_compare_t regex_canonical_equivalent(regex_cache_t *cache,
stringtable_index_t x,
stringtable_index_t y) {
if (x.value == y.value) {
return regex_compare_equal;
}
intstack_t stack = intstack_create(256);
if (!intstack_valid(stack)) {
return regex_compare_out_of_memory;
}
intmap_t map = intmap_create(16);
if (!intmap_valid(map)) {
intstack_destroy(stack);
return regex_compare_out_of_memory;
}
regex_compare_t res =
regex_canonical_equivalent_with_structures(cache, x, y, &stack, &map);
intstack_destroy(stack);
intmap_destroy(map);
return res;
}Instead, I'll advocate for a cleanup section at the end similar to
cleanup:
if (bar) bar_destroy(bar);
if (foo) foo_destroy(foo);
if (map) intmap_destroy(map);
if (stack) intstack_destroy(stack);
return res;
And I would replace all the if (!something) with, e.g. if (!map) {
res = regex_out_of_memory;
goto cleanup;
} regex_compare_t regex_canonical_equivalent(regex_cache_t *cache,
stringtable_index_t x,
stringtable_index_t y) {
if (x.value == y.value) {
return regex_compare_equal;
}
intstack_t stack = intstack_create(256);
if (!intstack_valid(stack)) goto out_of_memory;
intmap_t map = intmap_create(16);
if (!intmap_valid(map)) goto out_of_memory;
return regex_canonical_equivalent_with_structures(cache, x, y, &stack, &map);
out_of_memory:
if (intstack_valid(stack)) intstack_destroy(stack);
if (intmap_valid(map)) intmap_destroy(map);
return regex_out_of_memory;
}
Better yet if your data structures have "destroyvalid" methods. intstack_t stack = intstack_default();
intmap_t map = intmap_default();
regex_compare_t res = regex_out_of_memory;
stack = intstack_create(256);
if (!intstack_valid(stack)) goto out_of_memory;
map = intmap_create(16);
if (!intmap_valid(map)) goto out_of_memory;
res = regex_canonical_equivalent_with_structures(cache, x, y, &stack, &map);
out_of_memory:
intstack_destroy(stack);
intmap_destroy(map);
return res;
It should be possible to structure things so one doesn't accumulate stack frames and also keeps the resource logic separate from the implementation but it's not immediately obvious to me what a pretty way to do that is.It's not particularly bad because there are only three possible resource allocation failures in your example, but it still illustrates a problem that will be worse when applying the approach as a general strategy. Consider a case where regex_canonical_equivalent_with_structures needed two stacks and two maps:
intstack_t stack_a = intstack_create(256);
if (!intstack_valid(stack_a)) {
return regex_compare_out_of_memory;
}
intstack_t stack_b = intstack_create(256);
if (!intstack_valid(stack_b)) {
intstack_destroy(stack_a);
return regex_compare_out_of_memory;
}
intmap_t map_a = intmap_create(16);
if (!intmap_valid(map_a)) {
intstack_destroy(stack_a);
intstack_destroy(stack_b);
return regex_compare_out_of_memory;
}
intmap_t map_b = intmap_create(16);
if (!intmap_valid(map_b)) {
intstack_destroy(stack_a);
intstack_destroy(stack_b);
intmap_destroy(map_a);
return regex_compare_out_of_memory;
}
regex_compare_t res =
regex_canonical_equivalent_with_structures(cache, x, y, &stack_a, &stack_b &map_a, &map_b);
intstack_destroy(stack_a);
intstack_destroy(stack_b);
intmap_destroy(map_a);
intmap_destroy(map_b);
return res;
}You now have a 5-tuple maintenance problem and the code isn't particularly readable. I call it a "wet staircase"; it's not DRY, it's easy to slip and it gets taller for each step :)
Consider the equivalent using a simple goto escape hatch:
regex_compare_t regex_canonical_equivalent(regex_cache_t *cache,
stringtable_index_t x,
stringtable_index_t y) {
if (x.value == y.value) {
return regex_compare_equal;
}
intstack_t stack_a = intstack_create(256);
if (!intstack_valid(stack_a)) goto out_of_memory;
intstack_t stack_b = intstack_create(256);
if (!intstack_valid(stack_b)) goto out_of_memory;
intmap_t map_a = intmap_create(16);
if (!intmap_valid(map_a)) goto out_of_memory;
intmap_t map_b = intmap_create(16);
if (!intmap_valid(map_b)) goto out_of_memory;
return regex_canonical_equivalent_with_structures(cache, x, y, &stack_a, &stack_b &map_a, &map_b);
out_of_memory:
if (intstack_valid(stack_a)) intstack_destroy(stack_a);
if (intstack_valid(stack_b)) intstack_destroy(stack_b);
if (intmap_valid(map_a)) intmap_destroy(map_a);
if (intmap_valid(map_b)) intmap_destroy(map_b);
return regex_out_of_memory;
}
Or if you add "destroyvalid" methods to your data structures, out_of_memory:
intstack_destroyvalid(stack_a);
intstack_destroyvalid(stack_b);
intmap_destroyvalid(map_a);
intmap_destroyvalid(map_b);
return regex_out_of_memory;
}IIRC at least one of the revisions of MISRA specifically allows for using goto to break forward to a cleanup-and-return block, but others don't.
In a higher-level language, it's as silly as asking if you should avoid explicit malloc/free when writing SQL.
How would the Rust compiler deal with goto? You could dodge ownership and start operating on data before/after it's created/deleted.
A try-with-resources/using in Java/C# would no longer offer its guarantees.
How about async blocks in general? Do you just wander into another thread's code (but keep your own stack frame?)
Lambdas? Closures?
People probably
> regurgitate the age old argument against the use of goto
because there's no new arguments for it.
While people are familiar with Functional and OOP, most forgot or don't know about structured programming. Because it has proven to be good by default almost universally.
Look at the COBOL ALTER statement or GCC computed GOTO to see what the issues were.
While there are Böhm–Jacopini theorem purists that argue break and return, it is the law of diminishing returns.
Break and return may be syntactic sugar for GOTO, but they don't have the same problem of the unrestricted transfer of control that was and is considered harmful.
GOTOs are just one of the oldest ones, but these days, for example, we have polymorphism, and OO, in general, and, for some folks, anything that is \(!$HOLY_MANNA) is bad.
In my work, I tend to mix brand-new techniques with very old ones (probably older than some of the folks reading these words of prose).
Writing Solid Code was written in 1992. Many of its techniques have been integrated into toolchains, some are outdated, and some are still every bit as valid, today, as they were, then.
I will say that reading it was a watershed, for me.
That same book also teaches something important: programs based only on control structures like if and while generate goto graphs that meet a certain important property. All non-forward gotos in the graph are backwards gotos, dominated by the statement to which they branch.
A situation like this isn't generated by control structures; it requires goto:
if (condition0) {
statement1;
label0:
statement2;
}
if (condition1) {
goto label0;
}
What happens now that statement2 is reachable in a way that doesn't require/entail the execution of statement1.The "goto label0" is not a true backwards goto.
Backwards gotos return to nodes which dominate the goto statement; i.e. go back to a point in the program that had to be visited in order to reach the goto. Here, label0 doesn't dominate the goto at all. The goto can be reached without reaching that label.
The people who religiously avoid the use of gotos are more often than not just cargo-culters.
It is just that "goto" is closer to assembly code, and therefore used in intermediary code representations inside the compiler for that reason. But if the compiler had made sure beforehand that the control flow is reducible (and don't break that in an intermediary pass), then it will remain so.
I find that most uses of gotos in source code are still reducible control-flow, but which had just not been expressible using structured programming constructs in the language. For example jumping out of nested loops, "for-else" and error handling. It is very rare that you see "spaghetti code" in practice.
1: https://en.wikipedia.org/wiki/Control-flow_graph#Reducibilit...
The horrors of BASIC with its spaghetti of GO TO or its mess of PEEKs and POKEs are a justification for permabanning that style of programming -- but a decade of typing in listings from magazines inspired the generation the brought us the web and pocket phones. Maybe it wasn't such a bad thing after all.
https://store.steampowered.com/app/300570/Infinifactory/
The feeling being squished into a corner by your own rat's nest is really something.
I have never experienced the need for a ‘goto’ otherwise.
Now, 1970’s COBOL, that was another story.
> - Using a single goto and single label to exit several levels of scope.
Better handled by putting the nested scope in a function and using `return`.
> - Using a goto in the middle of complex construction to short cut to the top of a loop.
Use `continue`.
> - Starting a highly optimized do { ... } while loop that is best initiated by jumping into the center first. (Because your compiler is not cooperating.)
This one's tricky, first be sure that the optimization is actually needed. Then, with a quick glance at DRY for apology put a copy of the second half of the loop before the `do`.
> - Extensions of duff's device.
Oh come on. The original Duff's device is not intuitive when first approaching it, and there is pretty much one use for it. I wouldn't even suggest to juniors that they be aware of this trick at all, tbh. It's a curiosity but I balk at it being a justification for adding gotos to make your own custom extension of it. See above, check that you really need such a severe optimization. You likely don't.
> Better handled by putting the nested scope in a function and using `return`.
I dunno, that seems pretty silly to me. It's a perfectly valid use case of goto to jump out of deeply nested loops, and refactoring it to break out the inner loop in a function seems like pointless busy-work that ultimately makes the code harder to read, just so you can avoid a goto.
My solution for a long time was to add the test within the "for" itself, but it is often confusing for users.
But why is that better than just using a goto? Goto’s are usually discouraged because they make code hard to follow, but if avoiding the goto makes the code even more convoluted, whats the point?
At any rate, the greatest danger in this case lies in the code duplication. If performance is not an issue, stick it in a function. If performance may be an issue, start off by sticking it in a function and see if compiler optimizations take care of the issue. If performance is an issue, by all means force it to do what you want by using `goto`.
While there is nothing intrinsically evil about `goto`, most of the developments in programming languages were meant to overcome challenges of the past. We shouldn't dump those developments based upon exceptions to the rule. Rather, we should recognize exceptions to to rule as being extraordinary circumstances in which those developments were not useful.
You could also just use a goto and avoid the accidental complexity.
I still dont understand why a complicated workaround is better than a simple goto.