Carpe Diem

備忘録

Goのnilについて

概要

先日の golang.tokyo #6 - connpass で独自のエラー型でnilにハマった点に触れられていて、自分でもふわっとしか覚えてなかったのでまとめ。

覚えること

以下を覚えておけばとりあえず大丈夫です。

  • nilは型を持つ
  • interfaceの場合のみ、型もnilでないとxxx == nilはfalse

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でないと== nilfalse

先の例で分かるように、interface以外の型であれば型がついていても== niltrueになります。

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

arrarr2nilの型も値も同じですが、arr2はinterfaceとして扱っており、その場合は型もnilでないと== nilfalseになります。


独自型がとある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が通らないケースです。
理由はこれまでと同じで、

  • nilに型が入ってしまっている
  • interface型の場合、型のある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")  // 通る
    }
}

The Go Playground

ちょっと勘違いしそうですが、interfaceを実装した独自structを代入しない状態であれば、まだnilです。

まとめ

以下のことを覚えておけばハマることは回避できると思います。

  • nilは型を持つ
  • interfaceの場合のみ、型もnilでないとxxx == nilはfalse

ソース