メソッド

ポインタ vs 値

ByteSizeで見たように、メソッドは任意の名前付き型(ポインタまたはインターフェースを除く)に定義できます。レシーバーは構造体である必要はありません。

上記のスライスに関する議論で、Append関数を書きました。代わりに、スライスでメソッドとして定義できます。これを行うには、まずメソッドを結合できる名前付き型を宣言し、次にそのメソッドのレシーバーをその型の値にします。

type ByteSlice []byte

func (slice ByteSlice) Append(data []byte) []byte {
    // 本体は上で定義されたAppend関数とまったく同じです
}

これでも、メソッドが更新されたスライスを返す必要があります。その不器用さを解消するために、ByteSliceへのポインタをレシーバーとして取るようにメソッドを再定義できます。これで、メソッドは呼び出し元のスライスを上書きできます。

func (p *ByteSlice) Append(data []byte) {
    slice := *p
    // 上記のように本体、returnなし
    *p = slice
}

実際、さらに良くすることができます。関数を標準のWriteメソッドのように見えるように修正すると、次のようになります:

func (p *ByteSlice) Write(data []byte) (n int, err error) {
    slice := *p
    // 再び上記のように
    *p = slice
    return len(data), nil
}

これで、型*ByteSliceは標準インターフェースio.Writerを満たし、便利です。たとえば、1つに印刷できます。

var b ByteSlice
fmt.Fprintf(&b, "This hour has %d days\n", 7)

ByteSliceのアドレスを渡します。なぜなら、*ByteSliceのみがio.Writerを満たすからです。レシーバーに関するポインタ対値の規則は、値メソッドはポインタと値で呼び出すことができるが、ポインタメソッドはポインタでのみ呼び出すことができるということです。

この規則は、ポインタメソッドがレシーバーを変更できるためです。値で呼び出すと、メソッドは値のコピーを受け取るため、変更は破棄されます。したがって、言語はこの間違いを禁止します。しかし、便利な例外があります。値がアドレス可能な場合、言語は値でポインタメソッドを呼び出す一般的なケースを処理し、アドレス演算子を自動的に挿入します。私たちの例では、変数bはアドレス可能なので、b.WriteだけでWriteメソッドを呼び出すことができます。コンパイラは、それを(&b).Writeに書き換えます。

ちなみに、バイトスライスでWriteを使うアイデアは、bytes.Bufferの実装の中心です。