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型の値を代入しようとしてコンパイルエラーとなる。
となる様子。
コメント