Go Wiki: メソッドセット

目次

はじめに

特定の型または値のメソッドセットは、Go では特に重要です。メソッドセットは、値がどのインターフェースを実装するかを決定します。

仕様

Go 言語仕様には、メソッドセットに関する 2 つの重要な条項があります。それらは以下のとおりです。

メソッドセット: 型には、メソッドセットが関連付けられている場合があります。インターフェース型のメソッドセットはそのインターフェースです。その他の名前付き型 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 の一部です。