The reason not to have goto
is (as some other people have alluded to) is that it's incompatible with destructors. This, among others, is why Go (a language with safe goto
) has defer
at function, not block, scope.
In order to lower goto
in a sensible way, you need to make sure that:
- You do not jump past initializers into regions where they are alive.
- You do not jump over destructors.
The former of these is unsafe, and the later is ostensibly a logic bug, but both are quire bad. Moreover, because destructors get lowered to an LLVM cleanup
instruction, there are real caveats around codegen (since cleanup
emulates C++ goto
, which has this destructor preserving-behavior).
As @CAD97 observes, once you place sufficient restrictions on goto
, it turns into call/cc
with static continuations. In other words, all of the value you would ever want to get out of goto
is emulated perfectly by
'a: { ... break 'a ... }
This syntax does not exist today, but can be emulated using labeled loop
s; it is also a much-requested syntax. Backwards jumps are just a matter of adding another outer loop.
Given importantly, the primary uses of goto
in traditional imperative code are:
-
goto err
; we use the funny?
syntax for that. -
goto cleanup
; we use destructors, much like C++; other languages have invented weaker versions of this (Java'stry
-with, Python'swith
, Go'sdefer
, among many others). - Breaking/continuing out of an inner loop; C++ lacks break/continue-with-label, but every language since Java fixes this bug.
There are arguments about readability, but those are subjective, not compelling, and frankly a bit dismissive. There are significantly stronger arguments against it, and I'm someone who shows up in threads to tell people their proposed new feature is... not super readable.