goto — Yes, goto
There is a fourth control statement in Go, but chances are, you will never use it. Ever since Edgar 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 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. Yet Go has a goto statement. 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 4-23 shows two illegal goto statements. You can attempt to run it on The Go Playground.
Example 4-23. 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 what should you use goto for? 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 4-24 has a legal goto and demonstrates one of the few valid use cases.
Example 4-24. 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 our simple case, there is some logic that we don’t want to run in the middle of the function, but we do want to run the end of the function. There are ways to do this without goto. We could set up a boolean flag or duplicate the complicated code after the for loop instead of having a goto, but there are drawbacks to both of these approaches. 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 cannot find a way to restructure your logic, using a goto like this actually improves your code.
If you want to see a real-world example, you can 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 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 |= uint641) « 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, while other conditions 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 all make the code harder to understand.
Tip
You should try very hard to avoid using goto. But in the rare situations where it makes your code more readable, it is an option.
Wrapping Up
This chapter covered a lot of important topics for writing idiomatic Go. We’ve learned about blocks, shadowing, and control structures, and how to use them correctly. At this point, we’re able to write simple Go programs that fit within the main function. It’s time to move on to larger programs, using functions to organize our code.