Go

【Go】インターフェースとnil

インターフェースのゼロ値は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と判定されます。

最後に

とにかくややこしく、複雑な感じがします。

コメントを残す

メールアドレスが公開されることはありません。

© DeNnie.Lab All Rights Reserved.