Why does Rust not support goto statements?

As the author of that previous thread ([Pre-RFC] Safe goto with value), I suppose I should chime in here, although I think I lost enough hair in the first debate so I'll keep it short.

  • Rust does not have a perfect way to simulate goto. Labeled blocks would almost work, but only if they can be referenced outside their scope (but still in the same function), which the current labeled block/loop feature is future compatible with, but does not directly support at the moment.
  • goto can be done in a safe way, and amounts to a state machine transformation. You can in most cases mimic it using loop { state = match state { ... } }, but this doesn't work in all cases, especially around complex borrowing situations, and it also comes with a runtime cost as this pattern is not yet optimized perfectly (there is a MIR-opt pass PR that was merged but I don't know how effective it is in practice). A direct translation to the MIR goto would solve both problems.
  • Syntax for goto is trickier than you might think, because it needs to be sufficiently signposted that it doesn't mislead readers. The main conclusion of the "safe goto with value" thread, at least from my perspective, was that the best syntax is a compiler-builtin macro that allows writing a bunch of states as if they were functions that call each other as tail calls, possibly using the reserved become keyword, in conjunction with a somewhat heavyweight fsm! wrapper to scope the whole thing. It is important that the syntax be heavyweight in order to discourage overuse (which I don't think is a concern in the modern era but is an important concession to Dijkstra fans), as well as to clearly delimit when gotos are happening and where they can go.

Also, the thread only covered gotos to statically known targets. Computed goto is a whole different ballgame, and I think it would be significantly more complex to support safe computed goto as this requires types like function pointer types but for local labels, which must be tail called, and are also lifetime-scoped to the current call frame and the variable initialization state. While it seems maybe possible, it would be a significant extension to the type system, and I would be inclined to settle for either doing it all in asm! or using regular function pointers.

21 Likes