goto jumps to a labeled statement in the same function. The compiler blocks two shapes of jump: into a block the code wasn't already inside, and past a variable declaration that's still in scope at the label.
A label like retry: marks a target. goto retry jumps straight to it, running the same way a for loop would with the same condition.
attempts := 0
retry:
attempts++
fmt.Println("attempt", attempts)
if attempts < 3 {
goto retry
}
fmt.Println("gave up after", attempts)Most jumps a goto could make read more clearly as a labeled break or continue. The case where goto earns its place: bailing out of a loop straight to a shared return path once a value turns up, skipping every check that follows it.
func firstPositive(nums []int) (int, bool) {
var result int
for _, n := range nums {
if n <= 0 {
continue
}
result = n
goto found
}
return 0, false
found:
return result, true
}result is declared before the found label, not after. Declaring it past the label would jump over a variable still in scope at that point, which the compiler rejects.
package main
import "fmt"
func firstPositive(nums []int) (int, bool) {
var result int
for _, n := range nums {
if n <= 0 {
continue
}
result = n
goto found
}
return 0, false
found:
return result, true
}
func main() {
n, ok := firstPositive([]int{-3, -1, 0, 4, 9})
fmt.Println(n, ok)
}The loop reaches 4, the first positive value in the slice, and jumps to found without evaluating 9.
$ go run main.go
4 true