How to goto in Go?

December 2017

Despite Dijkstra’s famous words, Go actually contains the goto keyword. Why? Do the creators of Go not know that goto is harmful? ;-)

I had not really noticed or paid much attention to goto, but in reading some of the standard library I stumbled upon the use of it and decided to have a closer look.

ag "goto " /usr/local/go -l --go
/usr/local/go/src/encoding/xml/xml.go
/usr/local/go/src/go/build/build.go
/usr/local/go/src/go/constant/value.go
/usr/local/go/src/go/printer/printer.go
/usr/local/go/src/go/types/return.go
# and 120 more files
vim -p $(!!)

I have tried to extract what I found and group the common themes it into different sections.

Error

Some functions get really really long with lots of nested loops, conditionals, and switches, so this is common: Make an unreachable common error label at the bottom, that perhaps does some cleanup, and instead of returning and error from everywhere within the code, you just goto error instead.

This has the added benefit that if the function signature changes (say, to return an int and an error), then you only have to change it in one place.

func doSomething () (string, error) {
    for i, n := range ns {
        switch n {
        case SomithingBad:
            goto error
        }
    }

    return "foo", nil

error:
    return "", fmt.Errorf("No good :(")
}

Done

A done (or out) label that can be jumped to in case there were some cleanup or error checking before being able to return.

Notice that the done label is not unreachable without specifically going to it. When the loop ends, it will go there naturally.

func foo () error {
    for _, x := range y {
        switch z := x.(type) {
        case A:
            // do something with z
            goto done
        }
    }
done:
    if something() {
        return fmt.Errorf("No good :(")
    }
    return nil
}

Found

When searching for something; either by looping through a list (or list of lists) or traversing a tree, a found label is placed below the traversing, just below some error. In case the traversing never hits a goto Found the function will naturally hit the error.

Here is an example from src/compress/gzip/gunzip_test.go:

func TestMultistreamFalse(t *testing.T) {
    // Find concatenation test.
    var tt gunzipTest
    for _, tt = range gunzipTests {
        if strings.HasSuffix(tt.desc, " x2") {
            goto Found
        }
    }
    t.Fatal("cannot find hello.txt x2 in gunzip tests")

Found:
    // ~25 lines of testing
}

Again

This example speaks for itself. From math/rand with most of the comments stripped:


func (r *Rand) Float64() float64 {
again:
    f := float64(r.Int63()) / (1 << 63)
    if f == 1 {
        goto again // resample; this branch is taken O(never)
    }
    return f
}

Could a for loop have been used? Or perhaps it could call itself recursively. Sure; but they went with the goto.

scanner.Scan also has a good example of this, though named redo.

Loop

As a direct alternative to a for loop. I found these (at least to me) surprisingly often.

Here is an example from src/compress/flate/dict_decoder.go:

func (dd *dictDecoder) tryWriteCopy(dist, length int) int {
    dstPos := dd.wrPos
    endPos := dstPos + length
    if dstPos < dist || endPos > len(dd.hist) {
        return 0
    }
    dstBase := dstPos
    srcPos := dstPos - dist

    // Copy possibly overlapping section before destination position.
loop:
    dstPos += copy(dd.hist[dstPos:endPos], dd.hist[srcPos:dstPos])
    if dstPos < endPos {
        goto loop // Avoid for-loop so that this function can be inlined
    }

    dd.wrPos = dstPos
    return dstPos - dstBase
}

Ending outer loop

Example:

for {
    for {
        // do stuff

        if x {
            goto next
        }
    }
next:
}

In JavaScript (where we don’t have goto), I usually would label the outer for loop and break that to jump out of both loops.

One or the other

A pattern I found a few places was that of first determining something based on the input to the function, and then gotoing to the appropriate section handling that type. A contrived example would be something like this:

func Abs(n int) int {
    if n < 0 {
        goto negative
    }
    goto positive
negative:
    return -n
positive:
    return n
}

Imagine the if branch to have multiple nested ifs and switches, and the two labels to contain a lot more code and it makes sense.

As part of conditional logic

This was the most interesting to me. I think these examples speak for themselves.

From src/cmd/compile/internal/gc/reflect.go:

func dtypesym(t *Type) *Sym {
    // 30 lines of code

    if myimportpath == "runtime" && (tbase == Types[tbase.Etype] || tbase == bytetype || tbase == runetype || tbase == errortype) { // int, float, etc
        goto ok
    }

    // named types from other files are defined only by those files
    if tbase.Sym != nil && !tbase.Local {
        return s
    }
    if isforw[tbase.Etype] {
        return s
    }

ok:
    // 300 liness of code
}

Or here is an entire function from src/cmd/compile/internal/gc/walk.go:

func convas(n *Node, init *Nodes) *Node {
    if n.Op != OAS {
        Fatalf("convas: not OAS %v", n.Op)
    }

    n.Typecheck = 1

    var lt *Type
    var rt *Type
    if n.Left == nil || n.Right == nil {
        goto out
    }

    lt = n.Left.Type
    rt = n.Right.Type
    if lt == nil || rt == nil {
        goto out
    }

    if isblank(n.Left) {
        n.Right = defaultlit(n.Right, nil)
        goto out
    }

    if !eqtype(lt, rt) {
        n.Right = assignconv(n.Right, lt, "assignment")
        n.Right = walkexpr(n.Right, init)
    }

out:
    ullmancalc(n)
    return n
}

Last one from src/cmd/dist/build.go:

files = filter(files, func(p string) bool {
    for _, suf := range depsuffix {
        if strings.HasSuffix(p, suf) {
            goto ok
        }
    }
    return false
ok:
    // more logic
})

Conclusion

I picked up Go about a year ago, and not once have I seen a goto statement in any of the code I’ve read. Not in examples on StackOverflow or in any of the libraries that I have read through.

Yet I think the above bits of code provide some solid examples of where clarity is not sacrificed but rather enhanced by the use of goto, and I am happy to add it to my arsenal of tools to use when writing Go code.