There are three easy to make mistakes in go. I present them here in the way they are often found in the wild, not in the way that is easiest to understand.
All three of these mistakes have been made in Kubernetes code, getting past code review at least once each that I know of.
- Loop variables are scoped outside the loop.
What do these lines do? Make predictions and then scroll down.
funcprint(pi *int) { fmt.Println(*pi) }fori:=0; i < 10; i++ {defer fmt.Println(i)deferfunc(){ fmt.Println(i) }()deferfunc(i int){ fmt.Println(i) }(i)deferprint(&i)go fmt.Println(i)gofunc(){ fmt.Println(i) }()
}
Answers:
funcprint(pi *int) { fmt.Println(*pi) }fori:=0; i < 10; i++ {defer fmt.Println(i) // OK; prints 9 ... 0deferfunc(){ fmt.Println(i) }() // WRONG; prints "10" 10 timesdeferfunc(i int){ fmt.Println(i) }(i) // OKdeferprint(&i) // WRONG; prints "10" 10 timesgo fmt.Println(i) // OK; prints 0 ... 9 in unpredictable ordergofunc(){ fmt.Println(i) }() // WRONG; totally unpredictable.
}forkey, value:=range myMap {// Same for key & value as i!
}
Everyone expects these values to be scoped inside the loop, but go reuses the same memory location for every iteration. This means that you must never let the address of key
, value
, or i
above escape the loop. An anonymous func() { /* do something with i */ }
(a "closure") is a subtle way of taking a variable's address
- Nil interface is not the same as having a nil pointer in the interface.
typeCatinterface {Meow()
}typeTabbystruct {}func(*Tabby) Meow() { fmt.Println("meow") }funcGetACat() Cat {varmyTabby *Tabby = nil// Oops, we forgot to set myTabby to a real valuereturn myTabby
}funcTestGetACat(t *testing.T) {ifGetACat() == nil {
t.Errorf("Forgot to return a real cat!")
}
}
Guess what? The above test will NOT detect the nil pointer! This is because the interface serves as a pointer, so GetACat effectively returns a pointer to a nil pointer. Never do the thing that GetACat does, and you (and your coworkers) will be much happier. It is very easy to do this with error values. http://golang.org/doc/faq#nil_error
- Shadowing considered harmful to your sanity.
varErrDidNotWork = errors.New("did not work")funcDoTheThing(reallyDoItbool) (errerror) {if reallyDoIt {result, err:=tryTheThing()if err != nil || result != "it worked" {
err = ErrDidNotWork
}
}return err
}
The above function will always return a nil error, because the inner err variable "shadows" the function scope variable. Adding a var result string
and not using :=
would fix this.