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 goto
ing 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 if
s and switch
es, 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.