メソッド
ポインタ 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
の実装の中心です。