Tasteless uses for goto

Last updated 1999-02-23.

Dignity-preserving disclaimer

I haven't programmed in BASIC in years, and I never did most of this stuff. I simply wanted to explore possible idioms using `goto' that didn't show up in programs written by programmers educated in `structured' programming. Please mail me if you have seen other uses of goto.

I think that some of these might actually be OK under some circumstances, but some of them are definitely creeping horrors from the dark ages. Please don't read further unless you are an experienced programmer and have a strong stomach. (If you are not an experienced programmer, you should learn to write good code before you see this stuff.)

The basics

The traditional `structured' combinations are sequence (which doesn't require gotos), alternation, and closure. Alternation with gotos looks like this:

100 IF A = 3 THEN 130
110   REM IF A DOESN'T = 3 THEN DO STUFF HERE
120 GOTO 140
130   REM IF A = 3 THEN DO STUFF HERE
140 REM EXECUTION CONTINUES HERE

Closure -- while (a) { b; } in C -- can take two forms:

200 IF A = 0 THEN 230
210   REM LOOP BODY GOES HERE
220 GOTO 200
230 REM EXECUTION CONTINUES HERE

and the traditional form:

300 GOTO 320
310   REM LOOP BODY GOES HERE
320 IF A <> 0 THEN 310
330 REM EXECUTION CONTINUES HERE

The traditional form easily admits of a marginally-faster run-at-least-once version, which is in C as do { b; } while (a); and is not generally used by people who have not written it in the goto form:

350 REM STUFF BEFORE LOOP GOES HERE
360   REM LOOP BODY GOES HERE
370 IF A <> 0 THEN 360
380 REM EXECUTION CONTINUES HERE

Creative code placement

goto gives you the freedom to put the various chunks wherever you want, though. This can be useful if your BASIC line-numbering is inflexible (i.e. no RENUM) or if you're writing raw machine code (without an assembler). But more often, it just happens, making the program harder to read.

You can insert extra statements in a sequence like this:

400 GOTO 10000
401 REM PROGRAM CONTINUES HERE
420 REM REST OF PROGRAM GOES HERE . . . 
10000 REM CODE THAT COULD HAVE GONE ON LINE 400 GOES HERE
10010 REM BUT IT TOOK MORE THAN ONE LINE
10020 GOTO 401

After a while, the result is segments of code that look like this:

450 GOTO 760
451 GOTO 520
452 GOTO 2000
453 GOTO 2100

In a sense, this is a sort of subroutine call -- it's just that the subroutine is only called from one place, so it can use GOTO instead of RETURN to return. In itself, that's not so bad, except that the subroutine's name is a number, it is nontrivial to determine whether the GOTOs above really are `subroutine calls' or one of the other creeping horrors below, and trying to use the subroutine somewhere else requires that you modify it and possibly the call to it.

You can do similar things with alternation:

500 IF A = 3 THEN 10100
510 REM DO STUFF HERE IF A <> 3
520 REM REST OF PROGRAM GOES HERE
10100 REM DO STUFF HERE FOR A=3
10110 GOTO 520

The tricky thing about that one is that the "else" case -- where A <> 3 -- may be empty, and it's hard to tell where it ends. The more extreme case is a little better in that regard:

550 IF A = 3 THEN 10200 ELSE 10300
560 REM REST OF PROGRAM GOES HERE
10200 REM DO STUFF HERE FOR A = 3
10210 GOTO 560
10300 REM DO STUFF HERE FOR A <> 3
10310 GOTO 560

You can do all sorts of similar tricky stuff with closure:

600 IF A <> 0 THEN 10400
610 REM REST OF PROGRAM GOES HERE
10400   REM LOOP BODY GOES HERE
10410 GOTO 600
700 GOTO 10510
710 REM REST OF PROGRAM GOES HERE
10500   REM LOOP BODY GOES HERE
10510 IF A <> 0 THEN 10500

Those are two of the obvious variations.

Tail calls

Scheme has a nifty concept called a "tail call", wherein a call to another function as the last thing in a function can be optimized into a goto to the beginning of that other function. This means you can do infinite recursion without blowing up the stack.

You can, of course, do this yourself with gotos:

800 GOSUB 10600
810 REM REST OF PROGRAM GOES HERE
10600 REM ROUTINE FOO
10610 REM PUT FOO'S BODY HERE
10620 GOTO 10700
10700 REM ROUTINE BAR
10710 REM PUT BAR'S BODY HERE
10720 RETURN

This actually describes some of Knuth's examples of good uses of goto in his article ``Structured Programming with GO TO Statements'' -- the examples of his I remember were a recursive binary-tree search routine (made nonrecursive with GO TO) and a state-machine simulator (where the routine for each opcode ends with a GO TO NOP_INSTRUCTION statement).

Knuth's article was, I think, published before Scheme's inventors came up with a precise notion of what a ``tail call'' was.

I should note that tail-call elimination, which is a mandatory ``optimization'' in Scheme, is in general not possible in C. For compatibility and support for variadic functions, the stack area in which a called function's parameters are passed is both created and destroyed by the caller in programs written in C. This is because the caller (obviously) has to build that parameter block, and in C, there's no way to guarantee that the caller could see a prototype of the called function, and so the caller may have pushed the wrong number of parameters -- only the caller knows for sure. So the callee can't tear down its own stack frame and replace it with the stack frame of a callee.

Fortunately, in C, you can't goto another routine (essentially for the same reason), so the above goto idiom is considerably more readable in C than in BASIC.

Subroutine returns

You can, as mentioned above, return from a subroutine called from only one place by using goto. But you can also return from a subroutine called from several places by using goto -- you just have to store the place to return to in a variable:

900 LET R1=1
910 GOTO 10800
920 REM EXECUTION RETURNS HERE
1000 LET R1=2
1010 GOTO 10800
1020 REM EXECUTION RETURNS HERE
1100 LET R1=3
1110 GOTO 10800
1120 REM EXECUTION RETURNS HERE
1200 LET R1=4
1210 GOTO 10800
1220 REM EXECUTION RETURNS HERE
1230 REM REST OF PROGRAM GOES HERE
10800 REM BODY OF SUBROUTINE GOES HERE
10810 IF R1 > 2 THEN 10840
10820 IF R1 = 1 THEN 920
10830 GOTO 1020
10840 IF R1 = 3 THEN 1120
10850 GOTO 1220

This is the creeping horror that made me want to write this page in the first place.

This obviously doesn't work well for recursive subroutines. For that, you need to maintain an `R1 stack' (either for each routine, or for all recursive routines).

Total subroutine elimination

You can often remove a subroutine by inlining it, if you so desire. If the subroutine is called in many places, you may expand the program, but you can still inline it in all those places.

But with the techniques above, you can inline all subroutines everywhere, even recursive ones.