Why we should bring back the goto statement

Anyone that that has done any kind of programming has probably heard some version of the following sentence:

The goto statement is considered harmful as it leads to spaghetti code, don't use it

And if you've seen a significant amount of codebases you'll also know that most people take this to heart. I've personally never come across anyone using a goto statement in anything higher level than C, and even in a C codebase it's quite rare.

Some modern developers have even started taking this to the next level by implementing scuffed versions of goto into their languages or snuffing it out entirely. Some languages that don't have a goto statement:

Webassembly!!! yes, that's an assembly language without goto. This is shocking because goto is the atomic logic that underlies structured programming statements like while , for , and switch What a compiler usually does is transform everything into goto 's and then pass it to an optimizer . The implementation of the optimizer is now simplified since it doesn't have to care about the higher level constructs.

So compilers end up outputting a whole bunch of goto 's which webassembly doesn't support, what now? This does nothing but artifically introducing complexity for the compiler develper since they now need to somehow reloop everything back into higher level constructs when targeting webassembly. This isn't such a big deal for already bloated and highly optimizing compilers like LLVM, but single pass compilers like TCC has no way of targeting webassembly in its current state. Supposedly the funclets proposal aims to solve this problem.


So goto 's are good for low level, but what about high level? Let's check out some code and see how the goto alternatives stack up.

for vs while

A for loop can easily be replaced by a while loop, and we can break the while loop down further into a goto :

// for loop
for (int i = 0; i < 100; i++) {
    // ...
}

// while replacement
int i = 0; while (i < 100) {
    // ...
    i++;
}

// goto replacement
int i = 0;
start:
// ...
i++;
if (i < 100) goto start; 

Notice how the for loop is nice and readable, and as we descend to lower level constructs the information we care about gets sprawled out all over the place.

The trap one might fall into is thinking that the lower level construct is always inferior to the higher level one, but this is just not true. for only implements a subset of goto 's functionality, and some problems are just more elegantly solved by dropping down to goto .

Here's a simplified example from a syntax highlighter I've been writing recently:

size_t i = 0;     // current index
size_t start = 0; // start of the current (unmatched) text

while (code[i] != NULL) {
    // ...
    if (match("//", cursor) ) {
        emit_unmatched_text();
        // every character on the line will be part of the comment
		skip_line(code, &i);
		emit((lowdark_token){ LOWDARK_COMMENT, cursor, i });
		goto matched;
    }
    // parse operators, keywords, symbols...

    unmatched:
        // advance by 1 character
		i++;
		continue;

	matched:
		start = i;
} 

Uh oh, we just used used a goto , let's try and get rid of it.

// ...
while (code[i] != NULL) {
    // ...
    if (match("//", cursor) ) {
        // ...
		emit((lowdark_token){ LOWDARK_COMMENT, cursor, i });
		start = i;
		continue;
    }
    // parse operators, keywords, symbols...

    // unmatched, advance by 1 character
	i++;
} 

We now need to duplicate start = i'; continue; for every block, not great. And what's this?

// ...
while (code[i] != NULL) {
    // ...
    if (match("//", cursor) ) {
        // ...
		emit((lowdark_token){ LOWDARK_COMMENT, cursor, i });
		start = i;
		continue;
    }

	for(int j = 0; operators[j] != NULL; j++) {
		if (match(operators[j], cursor)) {
            // ...
			cb((lowdark_token){ LOWDARK_OP, code+start, i-start });
		    start = i;
		    continue;
		}
	}

    // parse keywords, symbols...

    // unmatched, advance by 1 character
	i++;
} 

This won't work! now the continue will continue the inner loop instead of the outer one. Perhaps a matched flag can help?

// ...
while (code[i] != NULL) {
    // ...
    bool matched = false;
    if (match("//", cursor) ) {
        // ...
		emit((lowdark_token){ LOWDARK_COMMENT, cursor, i });
        matched = true;
    }

	for(int j = 0; operators[j] != NULL; j++) {
		if (match(operators[j], cursor)) {
            // ...
			cb((lowdark_token){ LOWDARK_OP, code+start, i-start });
            matched = true;
		}
	}

    // parse keywords, symbols...

    if (matched) {
		start = i;
    } else {
        // advance by 1 character
	    i++;
    }
} 

We're back to the level of clarity that the goto solution had, great! Except...

The code doesn't work!

while we set matched to true , our parser doesn't immediatly jump to the matched block so it keeps just keeps walking through the remaining code. This code is dependent of the state at the top of the while loop not being tampered with, so if a previous block already matched and tampered with the variables we'll be in trouble.

So how do you refactor this code propely? Probably by plitting the comment/keyword/operator parsers into their own functions which might also create overhead if the compiler doesn't inline them.

So the real question becomes: why do you want to refactor this code? Do you actually have a reason other than goto bad ? Because as far as I know, the only reason this code is considered wrong is because goto bad .

Which brings us to the origin of it all, what if the origins of goto statement considered harmful wasn't actually about the goto statement being harmful?


As pointed out in This article the infamous article isn't really calling goto itself harmful, it rather proposes that jumping to other procedures using goto is harmful as it messes up the call stack. These days this argument is no longer relevant as no modern high level language even allows you to jump to other procedures using goto .

Another piece of evidence that supports the usefulness of goto is its inclusion in the go programing language despite one of its main goals being simplicity.

You can also find goto being used in go's math library