【Go言語】Interface型の変数に代入するときのレシーバ毎の挙動について

Go

Interfaceを宣言するときのレシーバが値かポインタかで、Interface型の変数に代入するときの挙動が異なる。

値レシーバで宣言した時

値型でもポインタ型でもIインターフェース型の変数fooに代入することができる。

type I interface {
	fizz()
}

type Foo struct {}

// 値レシーバにfizz()を実装
func (f Foo) fizz() {
	fmt.Println("value fizz()")
}

func main() {
	var foo I

	fooValue := Foo{}
	fooPointer := &Foo{}

	foo = fooValue
	foo = fooPointer  // ポインタレシーバにはbizz()未実装だがIインターフェース型の変数に代入できる。

	fmt.Println(foo)
}

// 上記はコンパイル可能

ポインタレシーバで宣言した場合

Iインターフェース型の変数に代入できるのはポインタ型のみ。

type I interface {
	fizz()
}

type Foo struct {}

// ポインタレシーバにfizz()を実装
func (f *Foo) fizz() {
	fmt.Println("pointer fizz()")
}

func main() {
	var foo I

	fooValue := Foo{}
	fooPointer := &Foo{}

	// コンパイルエラー
	// cannot use fooValue (variable of type Foo) as I value in assignment: Foo does not implement I (method fizz has pointer receiver)
	foo = fooValue
	
	foo = fooPointer

	fmt.Println(foo)
}

// 上記はコンパイル不可

挙動の理由

The Go Programming Language Specification - The Go Programming Language

上記公式ドキュメントのMethod setsの説明によると

  • 型に定義されたメソッド群をメソッドセットという。
  • ある型TがあるインタフェースIを実装しているかどうかは、このメソッドセットを確認して判断する。
  • ある型Tのメソッドセットは、レシーバ型Tで宣言されたメソッドで構成される。
  • ある型Tのポインタ(*T)のメソッドセットは、レシーバTまたは*Tで宣言されたメソッドで構成される。

そのため

  • 値レシーバで宣言した場合
    Foo型・*Foo型の両メソッドセットにfizz()が含まれているため、両型ともIインターフェースを実装しているものと判断される。
  • ポインタレシーバで宣言した場合
    *Foo型のメソッドセットのみにfizz()が含まれるため、Iインターフェース型の変数fooにFoo型の値を代入しようとしてコンパイルエラーとなる。

となる様子。

コメント

タイトルとURLをコピーしました