Edited at

Golangにおけるnilの扱い方

バックエンドエンジニアの@yagi5です。

Goのnilについて書きました。

元記事


goにはnilが存在する。

他の多くの言語と違って、goのnilは型情報を保持している。

以下のようなコードを見てみよう。

package main

type s1 struct{}

type s2 struct{}

func main() {
var x *s1 = nil
var y *s2 = nil

compare(x, y)
}

func compare(x, y interface{}) {
println(x == y)
}

このコードを実行すると、 false が出力される。

これは、nilがそれぞれ型情報を保持しているからである。

以下のように修正してやると、trueを出力する。

package main

type s1 struct{}

type s2 struct{}

func main() {
var x *s1 = nil
var y *s1 = nil // 修正

compare(x, y)
return
}

func compare(x, y interface{}) {
println(x == y)
}

試しに、compare関数を外してみよう。

package main

type s1 struct{}

type s2 struct{}

func main() {
var x *s1 = nil
var y *s2 = nil
println(x == y)
}

前述の通り、falseが出力される。と思いきや、そうではない。

これは実は、コンパイルエラーになる。

prog.go:10:12: invalid operation: x == y (mismatched types *s1 and *s2)

では、nilチェックをどのように行えばよいか?

実は、nilはキャストできる。

package main

type s1 struct{}

func main() {
var x *s1 = nil

isnil(x)
return
}

func isnil(x interface{}) {
println(x == nil)
println(x == (*s1)(nil))
}

これを実行すると、

false

true

と出力される。

これらは、関数の仮引数の型をinterface{}にしていることによる。

以下のように修正すると、意図通りに動くだろう。

package main

type s1 struct{}

func main() {
var x *s1 = nil

isnil(x)
return
}

func isnil(x *s1) { // 仮引数の型を修正
println(x == nil)
println(x == (*s1)(nil))
}

現実的に、nilチェックを行うべきときに常にnilの型情報やキャストを意識しなければいけないのは非常に煩雑である。コードの独立性を下げることにもつながるだろう。

これを避けるには、明示的に型情報を持たないnilリテラルを返すのが望ましい。

つまり、

package main

import "fmt"

type s1 struct{}

func newS1() interface{} {
s := new(s1)
s = nil
return s
}

func main() {
s := newS1()
fmt.Println(s == nil)
}

こうではなく、

package main

import "fmt"

type s1 struct{}

func newS1() interface{} {
_ = new(s1)
return nil
}

func main() {
s := newS1()
fmt.Println(s == nil)
}

このように、常に return nil という書き方をするようにすると良い。