Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationEventAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
211
Help us understand the problem. What is going on with this article?

More than 5 years have passed since last update.

@umisama

絶対ハマる、不思議なnil

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に関しては直感性が無く辛い。言語仕様を読めば、まぁソレっぽい事はちゃんと書いてあるんだけど・・・
また何か面白い挙動を見つけたら追記する。

211
Help us understand the problem. What is going on with this article?
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
211
Help us understand the problem. What is going on with this article?