インターフェースのゼロ値はnilです。
Goにおいてインターフェースとnilとの関係は少し複雑なようです。
目次
構造体とインターフェース
構造体はnil比較出来ない
構造体をインターフェースの実装として利用する事がよくあるので、そもそも構造体がnil比較出来るのかという確認をしたいと思います。
結論は構造体のゼロ値はnilではないので、このような比較は行えません。実行するとpanicになります。
package main
import (
"fmt"
)
// 犬の構造体の定義
type dog struct {
name string
}
func main() {
// 構造体においてこのようなnil比較は行えない
d := dog{}
if d == nil {
fmt.Println("nil")
}
}
インタフェースの実装
構造体をインターフェースの実装としてnil比較してみます。しかし、これもnil比較は行えません。これは構造体型として扱われるからだと推測します。
package main
import (
"fmt"
)
// インターフェースの定義
type myInterface interface {
getName() string
}
// 犬の構造体の定義
type dog struct {
name string
}
// 犬のメソット定義
func (d dog) getName() string {
return d.name
}
func main() {
// 構造体においてこのようなnil比較は行えない
d := dog{}
if d == nil {
fmt.Println("nil")
}
}
インタフェース型の明示
インタフェース型を明示するとnil比較が行えるようです。以下のようにnilEquals関数の引数でインターフェース型を明記しているので、インターフェース型として扱われると推測します。
package main
import (
"fmt"
)
// インターフェースの定義
type myInterface interface {
getName() string
}
// 犬の構造体の定義
type dog struct {
name string
}
// 犬のメソット定義
func (d dog) getName() string {
return d.name
}
func main() {
d := dog{}
nilEquals(d) // Not nil が表示される
}
func nilEquals(m myInterface) {
if m == nil {
fmt.Println("nil")
} else {
fmt.Println("Not nil")
}
}
具象型とインターフェース
具象型とnil
ポインタのゼロ値はnilです。ポインタ型を返す関数(NewDog)を作成します。
受け取ったポインタをnil判定する関数(nilEquals)へ渡し使用してみます。
package main
import (
"fmt"
)
// インターフェースの定義
type myInterface interface {
getName() string
}
// 犬の構造体の定義
type dog struct {
name string
}
// 犬のメソット定義
func (d dog) getName() string {
return d.name
}
func main() {
// "Not nil", "dog" が表示される
d := NewDog("dog")
nilEquals(d)
// nilDogはnilかと思いきや "Not nil" が表示され、panicになる
nilDog := NewDog("")
nilEquals(nilDog)
}
// dog構造体の生成
func NewDog(name string) *dog {
if name == "" {
return nil
}
var d dog
d.name = name
return &d
}
// nil判定
func nilEquals(m myInterface) {
if m == nil {
fmt.Println("nil")
} else {
fmt.Println("Not nil")
fmt.Println(m.getName())
}
}
panicが発生します。
NewDog関数はdog型ポインタを返却します。このdog型ポインタはインターフェースの実装になります。またその値はnilです。今回、nilEquals関数へ渡された値はインターフェースを実装している型がdog型ポインタで値がnilという事になります。インターフェースがnilである条件は、インターフェースを実装している型と値がnilであることです。ゆえにnil判定されないようです。なのでdog型ポインタはnilにも関わらずgetName()メソットが実行されるのでpanicになります。
インタフェース型の明示
ここでもまた、インターフェース型である事を明示します。関数(NewDog)の戻り値の型をインタフェース型にします。
package main
import (
"fmt"
)
// インターフェースの定義
type myInterface interface {
getName() string
}
// 犬の構造体の定義
type dog struct {
name string
}
// 犬のメソット定義
func (d dog) getName() string {
return d.name
}
func main() {
// "Not nil", "dog" が表示される
d := NewDog("dog")
nilEquals(d)
// "nil" が表示される
nilDog := NewDog("")
nilEquals(nilDog)
}
// dog構造体の生成
func NewDog(name string) myInterface {
if name == "" {
return nil
}
var d dog
d.name = name
return &d
}
// nil判定
func nilEquals(m myInterface) {
if m == nil {
fmt.Println("nil")
} else {
fmt.Println("Not nil")
fmt.Println(m.getName())
}
}
panicは発生しなくなります。
NewDog関数は明示的にインターフェース型を返却します。ですので、インターフェースのnilが返却されます。これは、インターフェースの型と値がnilになります。今回、nilEquals関数へ渡された値はインターフェースの型と値がnilなのできちんとnilと判定されます。
最後に
とにかくややこしく、複雑な感じがします。