ログイン中のQiita Team
ログイン中のチームがありません

Qiita Team にログイン
コミュニティ
OrganizationイベントアドベントカレンダーQiitadon (β)
サービス
Qiita JobsQiita ZineQiita Blog
Go
210
どのような問題がありますか?

この記事は最終更新日から5年以上が経過しています。

Organization

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

ユーザー登録して、Qiitaをもっと便利に使ってみませんか。
  1. あなたにマッチした記事をお届けします
    ユーザーやタグをフォローすることで、あなたが興味を持つ技術分野の情報をまとめてキャッチアップできます
  2. 便利な情報をあとで効率的に読み返せます
    気に入った記事を「ストック」することで、あとからすぐに検索できます
umisama
元イケメン高専生、今は現役イケメンエンジニア。 現実と鏡を直視しない事に定評がある。
fe2o3
ソフトウェア開発、教育、情報セキュリティ事業を通して、人とコンピュータのよい関係をつくる企業です。CTF大会開催のトータルソリューション「CTFKit」といった自社開発や、スタートアップに特化した受託開発など事業展開しています。
この記事は以下の記事からリンクされています
yasuo-ozuGo言語は沼からリンク
ri-zhiGo言語開門からリンク
過去の2件を表示する

コメント

(編集済み)
リンクをコピー
このコメントを報告

一応、コンパイラの実装は簡単に追っているので、なぜこう動くのかなというところは理解しているつもりです。型switchも「見える」と書いているつもりで、「型がある」という議論をしているわけではないつもりです。

あらゆる型に対して値がnilであるかを確かめるには、interface型に変換せず、直接 v == nil と書けば充分なのでは。

interfaceで受ける関数は(標準パッケージを含めて)たくさんあるので、そういう造りに書かないgolangは充分でないと考えますが如何でしょうか。

0
リンクをコピー
このコメントを報告

完全に蛇足でしたね。申し訳ない。

すみません、僕の書き方もよくありませんでした。失礼しました。

interfaceで受ける関数はtype switchやreflectを使っていて、現状それで事足りているのだからそれで充分だと思っています。

僕も、その通りとは思います。
とはいえ"直感的ではない"ことに引っかかる人が多そうだなぁというところです。

0
リンクをコピー
このコメントを報告

golangでは逆に型を持たないnilが書けないんじゃないかと。
nilリテラルは常に型推論できる書き方を強制されますし。
nilチェックは受け取った時に必ずしましょうという考え方がいいのかなと思いました。
(型付nilを型キャストしちゃいけない)

2
リンクをコピー
このコメントを報告

そうですね、そのように思っています。
問題は、interface{}型のようにnilチェックしたい型がなになのか分からない場合で、こういう時は受け取った時に直ぐにnilチェックできないんですよね。いや、厳密には出来るんですけれど、

if hoge == nil {〜}

の形だとコンパイルは通るが必ずfalseになり直感的な動きをしないので、reflect.Value(hoge).IsNil()を使わないといけない。ですので、"受け取った時のnilチェック"がハマりポイントになり得るなと言うところでした。

0
リンクをコピー
このコメントを報告

前のコメントはハマらないコツとしてコメントをしました。
以下のルールがコード全体で徹底されていれば、(実際標準ライブラリはそうなってる)
nilリテラルとの比較が常にマッチしない状況をすべて無くすことができます。

「nilになるかもしれない値を受け取ったら必ずnilリテラルと比較チェックし、
nilであれば改めてnilリテラルを返す(または代入)」

つまりhogeをアサインするときにnilリテラル使っていれば必ずhogeの型に型推論されたnilがアサインされ、
nilチェックは通常の方法で必ずできます。
当然そういった考慮をしていないコードが混ざればreflectを使わざるを得ないわけですが。

1
リンクをコピー
このコメントを報告

http://play.golang.org/p/LsfJDTt3X8 良い書き方と悪い書き方のサンプル

4
リンクをコピー
このコメントを報告

なるほど、理解できました。ありがとうございます。

0
どのような問題がありますか?
あなたもコメントしてみませんか :)
ユーザー登録
すでにアカウントを持っている方はログイン
記事投稿イベント開催中
Azure Kubernetes Serviceに関する記事を投稿しよう!
~
210
どのような問題がありますか?
ユーザー登録して、Qiitaをもっと便利に使ってみませんか。
この機能を利用するにはログインする必要があります。ログインするとさらに便利にQiitaを利用できます。
この機能を利用するにはログインする必要があります。ログインするとさらに便利にQiitaを利用できます。
  1. あなたにマッチした記事をお届けします
    ユーザーやタグをフォローすることで、あなたが興味を持つ技術分野の情報をまとめてキャッチアップできます
  2. 便利な情報をあとで効率的に読み返せます
    気に入った記事を「ストック」することで、あとからすぐに検索できます
ストックするカテゴリー