Go has a fourth control statement, but chances are you’ll never use it. Ever since Edsger Dijkstra wrote “Go To Statement Considered Harmful” in 1968, the goto statement has been the black sheep of the coding family. There are good reasons for this. Traditionally, goto was dangerous because it could jump to nearly anywhere in a program; you could jump into or out of a loop, skip over variable definitions, or pop into the middle of a set of statements in an if statement. This made it difficult to understand what a goto-using program did.
Most modern languages don’t include goto--but Go does. You should still do what you can to avoid using it, but it has some uses, and the limitations that Go places on it make it a better fit with structured programming.
In Go, a goto statement specifies a labeled line of code, and execution jumps to it. However, you can’t jump anywhere. Go forbids jumps that skip over variable declarations and jumps that go into an inner or parallel block.
The program in Example 3-24 shows two illegal goto statements. You can attempt to run it on The Go Playground.
Example 3-24. Go’s goto has rules
func main() {
a := 10
goto skip
b := 20
skip:
c := 30
fmt.Println(a, b, c)
if c > a {
goto inner
}
if a < b {
inner:
fmt.Println("a is less than b")
}
}
Trying to run this program produces the following errors:
goto skip jumps over declaration of b at ./main.go:8:4
goto inner jumps into block starting at ./main.go:15:11
So when should you use goto? Mostly, you shouldn’t. Labeled break and continue statements allow you to jump out of deeply nested loops or skip iteration. The program in Example 3-25 has a legal goto and demonstrates one of the few valid use cases. You can also find this code in the examples/good_goto directory.
Example 3-25. A reason to use goto
func main() {
a := rand.IntN(10)
for a < 100 {
if a%5 == 0 {
goto done
}
a = a*2 + 1
}
fmt.Println("do something when the loop completes normally")
done:
fmt.Println("do complicated stuff no matter why we left the loop")
fmt.Println(a)
}
This example is contrived, but it shows how goto can make a program clearer. In this simple case, there is some logic that you don’t want to run in the middle of the function, but you do want to run it at the end of the function. There are ways to do this without goto: You could set up a Boolean flag or duplicate the complicated code after the for loop instead of having a goto. However, both approaches have drawbacks. Littering your code with Boolean flags to control the logic flow is arguably the same functionality as the goto statement, just more verbose. Duplicating complicated code is problematic because it makes your code harder to maintain. These situations are rare, but if you can’t find a way to restructure your logic, using a goto like this can actually improve your code.
If you want to see a real-world example, take a look at the floatBits method in the file atof.go in the strconv package in the standard library. It’s too long to include here in its entirety, but the method ends with this code:
overflow:
// ±Inf
mant = 0
exp = 1<<flt.expbits - 1 + flt.bias
overflow = true
out:
// Assemble bits.
bits := mant & (uint64(1)<<flt.mantbits - 1)
bits |= uint64((exp-flt.bias)&(1<<flt.expbits-1)) << flt.mantbits
if d.neg {
bits |= 1 << flt.mantbits << flt.expbits
}
return bits, overflow
Before these lines, there are several condition checks. Some require the code after the overflow label to run; others require skipping that code and going directly to out. Depending on the condition, there are goto statements that jump to overflow or out. You could probably come up with a way to avoid the goto statements, but they would all make the code harder to understand.
Tip
Try very hard to avoid using goto--but, in the rare situations where it makes your code more readable, it is an option.