インターフェイスの割り当てがメソッド呼び出しよりも厳密なのはなぜですか?
Tour ofGoを行っています。ポインターをレシーバーとして受け入れるメソッドがある場合、値型もレシーバーとして受け入れることを学びました(自動的に変換を実行します)。
type Vertex struct { X, Y float64 }
func (v *Vertex) Abs() float64 {
return math.Sqrt(v.X * v.X + v.Y * v.Y)
}
次に、頂点が値またはポインタのどちらで受信されるかに関係なく、次のコードが有効です。
v := Vertex{1, 2}
fmt.Println(v.Abs())
p := &v
fmt.Println(p.Abs())
ただし、次のインターフェイスがあるとします。
type Abser interface {
Abs() float64
}
では、なぜ次のコードが無効なのですか?
var a Abser
v := Vertex{1, 2}
a = v // invalid
私の理解では、これで問題ないでしょう。vは、ポインターレシーバーを受け取るAbs関数を「実装」する値型ですが、値によっても取得しますか?
インターフェイスは、インターフェイス変数が右側で保持できるものに関して、より厳密になるように単純に設計されたのでしょうか。インターフェイスは* VertexとVertexを2つの異なるタイプとして認識していますが、Abs()メソッドにも問題の処理はありません。
回答
どちらの場合も、Goはメソッドを実行するためのポインターを必要としています。大きな違いは、メソッド呼び出しは自動的にのアドレスを取得しますがv
、何かがインターフェイスを実装しているかどうかを確認することはしません。
メソッド呼び出し:
プレーンタイプでポインターレシーバーを使用してメソッドを呼び出す場合、許可されている場合、Goはアドレスを自動的に取得します。メソッド呼び出しの仕様から(ハイライトは私のものです):
xのメソッドセット(のタイプ)にmが含まれ、引数リストをmのパラメーターリストに割り当てることができる場合、メソッド呼び出しxm()は有効です。xがアドレス可能で、&xのメソッドセットにmが含まれている場合、xm()は(&x).m()の省略形です。
この場合、x
は変数v
を参照し、アドレス指定可能です。したがって、メソッド呼び出しでは、Goが自動的に実行され(&v).Abs()
ます。
割り当て:
を割り当てよ うとする場合a = v
、満たす必要のあるチェックはT is an interface type and x implements T.
です。メソッドセットがインターフェイスと一致する場合にのみv
実装されます。このメソッドセットは次のように決定されます。Abser
他のタイプTのメソッドセットは、レシーバータイプTで宣言されたすべてのメソッドで構成されます。対応するポインター型* Tのメソッド・セットは、レシーバー* TまたはTで宣言されたすべてのメソッドのセットです(つまり、Tのメソッド・セットも含まれています)。
メソッドセットを計算するとき、Goはv
メソッド呼び出しで行ったようにのアドレスを取得しないことに気付くでしょう。これは、に設定されたメソッドvar v Vertex
が空であり、インターフェイスの実装に失敗したことを意味します。
解決:
これを回避する方法は、v
自分のアドレスを取得することです。
var a Abser
v := Vertex{1, 2}
a = &v
これを行うことにより、インターフェイスを*Vertex
含むメソッドセットを確認していることにAbs() float64
なりますAbser
。