Go Wiki: メソッドセット
目次
はじめに
特定の型や値のメソッドセットはGoにおいて特に重要であり、メソッドセットが値がどのインターフェースを実装するかを決定します。
仕様
Go言語仕様にはメソッドセットに関する2つの重要な条項があります。それらは以下の通りです。
メソッドセット: 型にはそれに関連付けられたメソッドセットを持つことができます。インターフェース型のメソッドセットはそれ自身のインターフェースです。他の名前付きtype Tのメソッドセットは、レシーバー型Tを持つすべてのメソッドで構成されます。対応するポインター型*Tのメソッドセットは、レシーバー*TまたはTを持つすべてのメソッドのセットです(つまり、Tのメソッドセットも含まれます)。その他の型は空のメソッドセットを持ちます。メソッドセットでは、各メソッドは一意の名前を持たなければなりません。
呼び出し: メソッド呼び出しx.m()は、xの(型の)メソッドセットがmを含み、引数リストがmのパラメーターリストに割り当て可能であれば有効です。xがアドレス可能で、&xのメソッドセットがmを含む場合、x.m()は(&x).m()の省略形です。
使い方
日々のプログラミングでは、メソッドセットが多くの異なるケースで現れます。主なものとしては、変数に対するメソッドの呼び出し、スライス要素に対するメソッドの呼び出し、マップ要素に対するメソッドの呼び出し、およびインターフェースへの値の格納などがあります。
変数
一般的に、ある型の変数がある場合、その変数に対してはほぼ好きなメソッドを呼び出すことができます。上記の2つのルールを組み合わせると、以下が有効です。
type List []int
func (l List) Len() int { return len(l) }
func (l *List) Append(val int) { *l = append(*l, val) }
func main() {
// A bare value
var lst List
lst.Append(1)
fmt.Printf("%v (len: %d)\n", lst, lst.Len())
// A pointer value
plst := new(List)
plst.Append(2)
fmt.Printf("%v (len: %d)\n", plst, plst.Len())
}
ポインターレシーバーメソッドも値レシーバーメソッドも、ポインター値と非ポインター値の両方で呼び出すことができることに注意してください。その理由を理解するために、両方の型のメソッドセットを仕様から直接調べてみましょう。
List
- Len() int
*List
- Len() int
- Append(int)
Listのメソッドセットには実際にはAppend(int)が含まれていないことに注目してください。上記のプログラムから問題なくメソッドを呼び出せるにもかかわらずです。これは上記の2番目の仕様セクションの結果です。それは暗黙的に以下の最初の行を2番目の行に変換します。
lst.Append(1)
(&lst).Append(1)
これでドットの前の値が*Listになったので、そのメソッドセットにはAppendが含まれ、呼び出しは合法になります。
これらのルールを覚えやすくするために、ポインターレシーバーメソッドと値レシーバーメソッドをメソッドセットとは別に考えることが役立つかもしれません。ポインター値メソッドは、すでにポインターであるもの、またはアドレスが取得できるもの(上記の例の場合)に対して呼び出すことができます。値メソッドは、値であるもの、または値がデリファレンスできるもの(任意のポインターの場合。このケースは仕様で明示的に指定されています)に対して呼び出すことができます。
スライス要素
スライスの要素は変数とほぼ同じです。アドレス可能であるため、ポインターレシーバーメソッドも値レシーバーメソッドも、ポインター要素スライスと値要素スライスの両方で呼び出すことができます。
マップ要素
マップの要素はアドレス可能ではありません。したがって、以下は**無効な**操作です。
lists := map[string]List{}
lists["primes"].Append(7) // cannot be rewritten as (&lists["primes"]).Append(7)
しかし、以下は依然として有効であり(そしてはるかに一般的なケースです)
lists := map[string]*List{}
lists["primes"] = new(List)
lists["primes"].Append(7)
count := lists["primes"].Len() // can be rewritten as (*lists["primes"]).Len()
したがって、ポインターレシーバーメソッドと値レシーバーメソッドの両方がポインター要素マップで呼び出せますが、値レシーバーメソッドのみが値要素マップで呼び出せます。これが、構造体要素を持つマップがほとんど常にポインター要素で作成される理由です。
インターフェース
インターフェースに格納される具象値は、マップ要素がアドレス可能でないのと同様に、アドレス可能ではありません。したがって、インターフェース上でメソッドを呼び出す場合、それは同一のレシーバー型を持つか、具象型から直接識別可能である必要があります。ポインターレシーバーメソッドと値レシーバーメソッドは、それぞれポインターと値で呼び出すことができます(期待通りに)。値レシーバーメソッドは、最初にデリファレンスできるため、ポインター値で呼び出すことができます。ただし、ポインターレシーバーメソッドは値で呼び出すことはできません。これは、インターフェース内に格納された値にアドレスがないためです。値をインターフェースに割り当てるとき、コンパイラはその値で可能なすべてのインターフェースメソッドが実際に呼び出せることを保証するため、不適切な割り当てを試みるとコンパイルに失敗します。以前の例を拡張すると、以下は有効なものと無効なものを記述しています。
type List []int
func (l List) Len() int { return len(l) }
func (l *List) Append(val int) { *l = append(*l, val) }
type Appender interface {
Append(int)
}
func CountInto(a Appender, start, end int) {
for i := start; i <= end; i++ {
a.Append(i)
}
}
type Lener interface {
Len() int
}
func LongEnough(l Lener) bool {
return l.Len()*10 > 42
}
func main() {
// A bare value
var lst List
CountInto(lst, 1, 10) // INVALID: Append has a pointer receiver
if LongEnough(lst) { // VALID: Identical receiver type
fmt.Printf(" - lst is long enough")
}
// A pointer value
plst := new(List)
CountInto(plst, 1, 10) // VALID: Identical receiver type
if LongEnough(plst) { // VALID: a *List can be dereferenced for the receiver
fmt.Printf(" - plst is long enough")
}
}
このコンテンツはGo Wikiの一部です。