いろいろ備忘録日記

主に .NET とか Go とか Flutter とか Python絡みのメモを公開しています。

Goメモ-342 (Go では nil 同士を比較しても同じにならない場合がある)(Nil isn't equal to Nil)

関連記事

GitHub - devlights/blog-summary: ブログ「いろいろ備忘録日記」のまとめ

概要

以下、自分用のメモです。周りのGo始めた人からよく聞かれる質問なのでついでにここにメモメモ。。。

Goでは nil 同士を比較しても「同じではない」という現象が発生することがあります。

なんかGoのややこしいところって形でよく紹介される部分ですね。

ですが正直、分かりにくい話だとは思うのですが、そんなに困ったことがないって感じです。

言葉で説明するより、実際のコードで見た方が分かりやすいと思いますので、以下にメモメモ。

サンプル

package main

import (
    "github.com/devlights/gomy/output"
)

func main() {
    if err := run(); err != nil {
        panic(err)
    }
}

func run() error {
    //
    // Go では、nil 同士を比較しても同じにならない場合がある
    //
    // REFERENCES:
    // - https://www.calhoun.io/when-nil-isnt-equal-to-nil/
    //  - https://forum.golangbridge.org/t/a-nil-a-b-b-nil-with-pointers-and-interface/10593
    //  - https://staticcheck.io/docs/checks#SA4023
    //  - https://devlights.hatenablog.com/entry/2021/03/31/235948
    //

    var (
        v1 *int // ゼロ値は nil
        v2 any  // ゼロ値は nil
    )

    // どちらもnil
    output.Stdoutl("[v1 is nil?]", v1 == nil)
    output.Stdoutl("[v2 is nil?]", v2 == nil)

    // v1とv2は同じ? (結果は false となる)
    //
    // staticcheck では以下の警告が出る
    //   this comparison is never true; the lhs of the comparison has been assigned a concretely typed value (SA4023)
    //
    output.Stdoutl("[v1 eq v2]", v1 == v2) //lint:ignore SA4023 It's ok because this is just a example.

    output.StdoutHr()

    //
    // v1をv2に設定してみる
    // (論理的には nil な変数に nil を設定していることになる)
    //
    v2 = v1

    // v1とv2は同じ? (結果は true となる)
    //
    // 代わりに、今度は v2 が nil ではないと判定される
    //
    // staticcheckでは今度は v2 == nil の部分で警告が出る
    //   this comparison is never true; the lhs of the comparison has been assigned a concretely typed value (SA4023)
    //
    output.Stdoutl("[v2=v1][v1 is nil?]", v1 == nil)
    output.Stdoutl("[v2=v1][v2 is nil?]", v2 == nil) //lint:ignore SA4023 It's ok because this is just a example.
    output.Stdoutl("[v2=v1][v1 eq v2]", v1 == v2)

    output.StdoutHr()

    // 大事な点として、Goのすべてのポインタには2つの基本情報があるということ。
    // ポインターの型と、ポインターが指す値である。
    var (
        v3 *int
        v4 any
    )

    output.Stdoutf("[v3]", "(%T,%v)\n", v3, v3) // (*int, <nil>)
    output.Stdoutf("[v4]", "(%T,%v)\n", v4, v4) // (<nil>, <nil>)

    // v3とv4を (型, 値) として見ると同じではない
    // なので、同じかどうかを聞くと同じとならない
    output.Stdoutl("[v3 eq v4]", v3 == v4) //lint:ignore SA4023 It's ok because this is just a example.
    output.StdoutHr()

    // ここで v3 を v4 に設定すると、値はnilのままだが、型が設定されるため同じになる
    v4 = v3
    output.Stdoutf("[v3]", "(%T,%v)\n", v3, v3) // (*int, <nil>)
    output.Stdoutf("[v4]", "(%T,%v)\n", v4, v4) // (*int, <nil>)
    output.Stdoutl("[v3 eq v4]", v3 == v4)
    output.StdoutHr()

    // v4 は any, つまり interface{} である
    // Go のインターフェースは 型と値 が、両方 nil の場合に nil となる
    // つまり型だけが設定されている場合は nil とならない
    output.Stdoutl("[v4 == nil?]", v4 == nil) //lint:ignore SA4023 It's ok because this is just a example.

    //
    // イディオムとして覚えておくことは、nilを設定したい場合は
    // 「nilな変数」を設定するのではなく、明示的に 「nil」 を設定すること
    //
    v4 = nil
    output.Stdoutf("[v4 = nil][v3]", "(%T,%v)\n", v3, v3) // (*int, <nil>)
    output.Stdoutf("[v4 = nil][v4]", "(%T,%v)\n", v4, v4) // (<nil>, <nil>)
    output.Stdoutl("[v4 = nil][v3 eq v4]", v3 == v4)      //lint:ignore SA4023 It's ok because this is just a example.

    // --------------------------------------------------
    // 実行結果
    // --------------------------------------------------
    // [v1 is nil?]         true
    // [v2 is nil?]         true
    // [v1 eq v2]           false
    // --------------------------------------------------
    // [v2=v1][v1 is nil?]  true
    // [v2=v1][v2 is nil?]  false
    // [v2=v1][v1 eq v2]    true
    // --------------------------------------------------
    // [v3]                 (*int,<nil>)
    // [v4]                 (<nil>,<nil>)
    // [v3 eq v4]           false
    // --------------------------------------------------
    // [v3]                 (*int,<nil>)
    // [v4]                 (*int,<nil>)
    // [v3 eq v4]           true
    // --------------------------------------------------
    // [v4 == nil?]         false
    // [v4 = nil][v3]       (*int,<nil>)
    // [v4 = nil][v4]       (<nil>,<nil>)
    // [v4 = nil][v3 eq v4] false

    return nil
}

実行すると以下のようになります。

gitpod /workspace/try-golang (main) $ task -d examples/singleapp/nil_is_not_equal_to_nil/
task: [run] go run main.go
[v1 is nil?]         true
[v2 is nil?]         true
[v1 eq v2]           false
-------------------------------------------------- 
[v2=v1][v1 is nil?]  true
[v2=v1][v2 is nil?]  false
[v2=v1][v1 eq v2]    true
-------------------------------------------------- 
[v3]                 (*int,<nil>)
[v4]                 (<nil>,<nil>)
[v3 eq v4]           false
-------------------------------------------------- 
[v3]                 (*int,<nil>)
[v4]                 (*int,<nil>)
[v3 eq v4]           true
-------------------------------------------------- 
[v4 == nil?]         false
[v4 = nil][v3]       (*int,<nil>)
[v4 = nil][v4]       (<nil>,<nil>)
[v4 = nil][v3 eq v4] false

リポジトリ

github.com

参考情報

www.calhoun.io

forum.golangbridge.org

Goのおすすめ書籍

Go言語による並行処理

Go言語による並行処理

Amazon


過去の記事については、以下のページからご参照下さい。

サンプルコードは、以下の場所で公開しています。