概要
先日の golang.tokyo #6 - connpass で独自のエラー型でnilにハマった点に触れられていて、自分でもふわっとしか覚えてなかったのでまとめ。
覚えること
以下を覚えておけばとりあえず大丈夫です。
nilを扱う型は
- pointer
- function
- map
- slice
- channel
- interface
がありますが、注意するのはinterfaceのみで大丈夫です。
解説
nilは型を持つ
nilは型
と値
の2つを持ちます。
func main() { var i32 *int32 fmt.Println(reflect.ValueOf(i32).IsNil()) // true fmt.Println(reflect.TypeOf(i32)) // *int32 var arr []int fmt.Println(reflect.ValueOf(arr).IsNil()) // true fmt.Println(reflect.TypeOf(arr)) // []int var m map[string]string fmt.Println(reflect.ValueOf(m).IsNil()) // true fmt.Println(reflect.TypeOf(m)) // map[string]string }
https://play.golang.org/p/xVU13_fA33
なので型の違うnil同士だと等値比較はfalseになります
func main() { var i32 *int32 fmt.Println(i32 == nil) // true var arr []int fmt.Println(arr == nil) // true fmt.Println(compare(i32, arr)) // false } func compare(first, second interface{}) bool { return first == second }
https://play.golang.org/p/S3Jq0O60Fk
※別関数で定義しているのは普通にif
でやろうとすると型が違うのでコンパイルエラーになるからです
interfaceの場合のみ、型もnilでないと== nil
はfalse
先の例で分かるように、interface以外の型であれば型がついていても== nil
はtrue
になります。
func main() { var arr []int fmt.Println(arr == nil) // true fmt.Println(reflect.ValueOf(arr).IsNil()) // true fmt.Println(reflect.TypeOf(arr)) // []int var arr2 interface{} = arr fmt.Println(arr2 == nil) // false fmt.Println(reflect.ValueOf(arr2).IsNil()) // true fmt.Println(reflect.TypeOf(arr2)) // []int var arr3 interface{} fmt.Println(arr3 == nil) // true fmt.Println(reflect.TypeOf(arr3)) // <nil> }
https://play.golang.org/p/c6L0fa8ZbN
arr
とarr2
はnilの型も値も同じですが、arr2
はinterfaceとして扱っており、その場合は型もnilでないと== nil
はfalse
になります。
独自型がとあるinterfaceを実装した場合あれば、同じことが起きる
このケースはinterface{}
だけでなく、別で定義されたinterface(今回はerror
)でも起きます。よくあるのはError()を実装した独自エラーです。
type MyError struct{} func (e MyError) Error() string { return "some error" } func main() { var err1 *MyError fmt.Println(reflect.ValueOf(err1)) // <nil> fmt.Println(reflect.TypeOf(err1)) // *main.MyError if err1 != nil { fmt.Println("error1 occured") // 通らない } var err2 error = err1 fmt.Println(reflect.ValueOf(err2)) // <nil> fmt.Println(reflect.TypeOf(err2)) // *main.MyError if err2 != nil { fmt.Println("error2 occured") // error2 occured } }
https://play.golang.org/p/mcLl2_-MWa
errorのinterfaceであるError()
を実装したMyError
を扱っています。
err1
はstruct型なので!= nil
が反応しませんが、後者はerrorというinterface型なので、型を持つとnilと判定されません。
一度でもinterface以外の独自型を経由させていると、nilリテラルを返しても== nilはfalse(その時点でnilに型が付与される)
よくハマるのがこのケースで、nilリテラルを返しているはずなのに== nil
が通らないケースです。
理由はこれまでと同じで、
です。
type MyError struct{} func (e MyError) Error() string { return "some error" } func MyErrorFunc() *MyError { return nil // nilリテラルを返す } func Wrapper() error { return MyErrorFunc() // nilを返している(ただし型は*MyError) } func main() { err := Wrapper() // interface型(error)のnilだけど、nil内部の型は*MyError fmt.Println(reflect.ValueOf(err)) // <nil> fmt.Println(reflect.TypeOf(err)) // *main.MyError if err == nil { fmt.Println("no error") // 通らない } }
https://play.golang.org/p/jVc0YfN4wO
逆にinterfaceでなく具体的な型として扱っていれば、== nil
は使える
interface型の場合、nilの内部の型までチェックしますが、逆にその独自型のままであれば== nil
は使えます。
type MyError struct{} func (e MyError) Error() string { return "some error" } func MyErrorFunc() *MyError { return nil // nilリテラルを返す } func Wrapper() *MyError { return MyErrorFunc() // nilを返している(ただし型は*MyError) } func main() { err := Wrapper() // *MyError型のnil fmt.Println(reflect.ValueOf(err)) // <nil> fmt.Println(reflect.TypeOf(err)) // *main.MyError if err == nil { fmt.Println("no error") // 通る } }
https://play.golang.org/p/W5pipNLkfO
なので1つ前のケースも、直接返すのでなく改めてnilを返せば問題ありません。
type MyError struct{} func (e MyError) Error() string { return "some error" } func MyErrorFunc() *MyError { return nil // nilリテラルを返す } func Wrapper() error { err := MyErrorFunc() if err != nil { // ここでハンドリング return err } return nil } func main() { err := Wrapper() // interface型(error)のnilで、型もnil fmt.Println(reflect.ValueOf(err).IsNil()) // <invalid reflect.Value> fmt.Println(reflect.TypeOf(err)) // <nil> if err == nil { fmt.Println("no error") // 通る } }
https://play.golang.org/p/makk31-_wj
独自interfaceは定義の段階ではnil
type MyInterface interface { do() string } func main() { var mi MyInterface fmt.Println(reflect.ValueOf(mi)) // <invalid reflect.Value> fmt.Println(reflect.TypeOf(mi)) // <nil> if mi == nil { fmt.Println("mi is nil") // 通る } }
ちょっと勘違いしそうですが、interfaceを実装した独自structを代入しない状態であれば、まだnilです。
まとめ
以下のことを覚えておけばハマることは回避できると思います。