Replacing flags with break or return statements can often be a good way of making things a bit clearer (of course, this is a bit subjective). As a rule of thumb, gotos that jump forward are not as bad as gotos that jump backwards.
You can get the same effect as goto without using the dreaded goto statement by combining a while loop and a large switch. Instead of 'goto nextstate', you just assign the state to the variable that controls the switch and fall through the loop one more time. A switch is basically syntactic sugar for a jump table.* This seems to be a fairly standard pattern when implementing, say, a virtual machine.
* Of course, with optimization, the switch may turn into something completely different.
ufo had already mentioned this technique a couple levels up. Though it's equivalent, I think it's worse than just using gotos.
Suddenly you have extra mutable state to worry about: the "next" state and a loop control variable. At that point, you've removed a layer of abstraction and introduced complexity. You've said "let the next state be foo, then repeat this loop, then if the next state is foo, then enter state foo" when you really meant: "go to state foo".
goto in this case is practically declarative, insofar as a state machine diagram is declarative -- code structured in this way is a one-dimensional representation of the diagram.
Though it's equivalent, I think it's worse than just using gotos.
I'm not sure I could judge what it would look like just using gotos, because I've never seen it that way. I think in virtual machine implementations, this may actually be the most rational structure for solving the problem at hand.
I agree with your general point, though. When you remove something essential from a programming language, you are forced to reinvent it; actually, more complex version(s) of it.
state1:
/* do some stuff */
if (/* something or other */)
goto state2;
else
goto state3;
state2:
/* do some stuff */
goto state1;
state3:
...
The labels represent states, and the gotos represent transitions.
You're right that this isn't particularly good for implementing virtual machines, but for more "fixed" types of state machines this can be a pretty clear way of representing them in code. Mike Pall wrote a good overview of various virtual machine techniques here: http://lua-users.org/lists/lua-l/2011-02/msg00742.html (while he knocks the performance of the switch-based approach, I'd like to note that it's the most portable).
> When you remove something essential from a programming language, you are forced to reinvent it; actually, more complex version(s) of it.
That's a good way of phrasing it. It almost sounds like a lemma to Greenspun's Tenth Rule :)
break statements are goto statements with implicit labels! To really get rid of gotos, you'll need to have a loop control variable:
...
int done = 0;
while (!done) {
...
done = 1;
...
}
Yes, it works fine and it's not terribly compelex. The version with goto is simpler because it's a more direct translation of the corresponding diagram.
If you want to find all the implicit gotos, just disassemble the compiled code. Your program will be littered with jmp instructions and the conditional versions of jmp. There's no getting away from them.
My main point was similar to something you'd said earlier:
> What we now know for absolute certain is that it's possible to write any kind of code without gotos (including awful code).
I meant to point out the inverse, that it's possible to write good code even with gotos -- but it takes the same amount of care and diligence as writing good code in general. I don't mean to advocate the use of gotos, rather the elimination of the paranoia surrounding them.
Just in case it's not clear, the break statements in my example are to break out of the switch(), not the while(). C's switch statements are like a computed goto.
Ah, right. I forgot about break inside switch. Thanks.
You're right about the switch statements being like a computed goto, albeit a delimited, forward-only one. I still think it's silly to bend over backwards to avoid using goto in places where goto is a natural fit.