絶対ハマる、不思議なnil

  • 156
    いいね
  • 9
    コメント
この記事は最終更新日から1年以上が経過しています。

goのnilは直感的ではない、これは強烈にハマりそう。

型を持つnil

nilと一口に書くが、実際には型がある。

nilとnilが等価でないように見える

nilが型情報を持つので、nil == nilがtrueになるとは限らない。
trueとなるためには、右辺と左辺の「nil」の型が一致しているという条件が必要。

package main

func main() {
        var x *int32 = nil
        var y *int64 = nil

        equals(x, y)
        return
}

func equals(x, y interface{}) {
        println(x == y)
}
> false

より凶悪なのが以下のようなパターンで、equals()内ではxがinterface{}として扱われているので、ここでnilを書くと左辺もinterface{}型のnilとして扱われる。
型が一致しないため、この結果もfalseとなる。

package main

func main() {
        var x *int32 = nil

        isnil(x)
        return
}

func isnil(x interface{}) {
        println(x == nil)
}
> false

nilはキャスト出来る

型があることを実感できる事実として、nilは型キャストすることが出来る。先のコードでfalseとなっていた例も、等価式のnilをキャストしてやればtrueを返せる。

package main

func main() {
        var x *int32 = nil

        isnil(x)
        return
}

func isnil(x interface{}) {
        println(x == (*int32)(nil))
}
> true

つまり、オブジェクトをinterface{}として扱っている場合、等価演算子のみを用いてもnilであることを確実に知ることはできない。

確実に「nil」を検出する

nilを検出するにはreflect.ValueのIsNil()を併用しなければならない。
ただし、reflect.ValueOf(nil)の場合はZeroValueが返却されるのでIsNil()が呼べない。なので、xがnilであるかの検査は先に行わなければならない。
# nilの型を意識する話なので補足しておくと、ここでreflect.ValueOf()はinterface{}型を引数として要求する。したがって、(interface{})(nil)の場合にZeroValueが返却される。

簡単に書けば以下のような感じ。
ちなみに、xがstructなどだとIsNil()がpanicになるので、実際には注意しなければいけない。

func isnil(x interface{}) {
        println( (x == nil) || reflect.ValueOf(x).IsNil() )
}

型かと思う振る舞いをするnil

先のようにnilは型情報を持つが、nilが型そのもののように振舞っているように見えるケースがある。それが型スイッチ構文を使う場合で、以下のようにcaseに型と一緒にnilを含めることが出来る。
このとき、nilのケースにはinterface型のnil(つまりinterface{}(nil))の場合にのみ入る。

package main

func main() {
        var x *int32 = nil

        typeswitch(x)
        return
}

func typeswitch(hoge interface{}) {
        switch hoge.(type) {
        case nil:
                println("hoge is nil")
        case *int32:
                println("hoge is *int32")
        case *int64:
                println("hoge is *int64")
        default:
                println("unknown")
        }
        return
}
> hoge is *int32

「hoge is nil」と出すためには、main()の中で宣言しているxをinterface{}型にしておかなければならない。これはLanguage Specificationの「Type switches」にわかりやすい説明がある。

わかりづらい

goは比較的直感的に使える言語だと思うけれど、nilに関しては直感性が無く辛い。言語仕様を読めば、まぁソレっぽい事はちゃんと書いてあるんだけど・・・
また何か面白い挙動を見つけたら追記する。