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
ではないのです。