https://www.yunabe.jp/docs/golang_pitfall.html

Go の言語仕様はシンプルで他の言語に比べてはまりどころが少なくて学習コストが小さめな言語のように思います。しかし、それでもはまるところがないわけではないので、自分がはまって時間を無駄にしてしまったことを書き留めておきます。

念の為誤解のないように追記しておくと、この文書の目的は Go を批判することではなく Go が Go であるがゆえに C++/Java/Python など利用者が Go を使い始めるときに困惑あるいは誤解するであろうポイントをまとめておくことで初めて Go を触る人がスムーズに Go を使い始められるようにすることです。私個人は Go はバランスがとれた良い言語でだと思いますし、気に入っています

目次

Java や C#あるいは C++の経験があり、ある程度クラスの内部構造への理解がある人は Go の interface の実体もデータへの参照だと考えると思います。 しかし実はそれは正しくありません。Go の interface の実体は参照(ポインタ)ではありません。Go の interface の実体は参照と型情報のペアです

さてこの内部構造の違いが Go にどういった影響をもたらすのでしょうか。実はこの内部構造の違い、意外と言語の挙動にはあまり大きな影響を与えません。そのためこの点を理解していなくても Go でプログラムを書けてしまいます。ただし nil を扱う場合には Go は予想外の挙動をします。

package main

import "fmt"
import "reflect"

type myError struct {
	message string
}

func (e *myError) Error() string {
	if e == nil {
		return "myError: <nil>"
	}
	return "myError: " + e.message
}

func myFunc(x int) error {
	var err *myError
	if x < 0 {
		err = &myError{
			message: "x should be positive or zero.",
		}
	}
	return err
}

func main() {
	err := myFunc(10)
	fmt.Println(err)
	if err != nil {
		fmt.Println("err is NOT nil.")
	} else {
		fmt.Println("err is nil.")
	}

	fmt.Println("---- err ----")
	fmt.Println("is nil:", err == nil)
	fmt.Println("Type:", reflect.TypeOf(err))
	fmt.Println("Value:", reflect.ValueOf(err))

	var trueNil error
	fmt.Println("---- trueNil ----")
	fmt.Println("is nil:", trueNil == nil)
	fmt.Println("Type:", reflect.TypeOf(trueNil))
	fmt.Println("Value:", reflect.ValueOf(trueNil))
}

上のコード(Go Playground)では、myFunc はx >= 0の時にはvar err *myErrorの初期値nilを返すので、main の最初のfmt.Println(err)myError: <nil>を出力します。そして、次の if-else は err が nil だから"err is nil."が表示されると思うかもしれません。Java や C++ならそうなります。しかし Go では"err is NOT nil"が表示されます。

何故こうなるのかは、interface が型と値への参照のペアであること点を踏まえた上で、err の Value と Type をreflect.ValueOf, reflect.TypeOfを使って表示してみると明らかです。 err の Type と Value を表示してみると、Type が*main.myErrorで値が Value が<nil>であることが分かります。err の"値"はnilですが err は型情報を保持しているのです。 型を持っている interface は Value がnilでもnilではないのです。