実践Go言語(Effective Go)の翻訳、9回目です。
前回までの訳は実践Go言語[日本語訳]にまとめてあります。


メソッド

ポインタ vs. 値

メソッドは、名前がつけられていればポインタとインタフェースを除くすべての型に定義することができます。(レシーバは構造体である必要はありません。)

以前、スライスの説明のところで書いたAppend関数を今度はスライスのメソッドとして定義してみます。これにはまず、メソッドと結びつけるために新たに名前付きの型を宣言し、メソッドにこの型のレシーバを定義します。

type ByteSlice []byte

func (slice ByteSlice) Append(data []byte) []byte {
    // 本体部分は前と全く同じ
}

このままでは、更新したスライスをメソッドから返す必要がまだ残っています。レシーバとしてByteSliceへのポインタを受け取るようメソッドを定義しなおすことによって、その問題を回避できます。こうすれば呼び出し元のスライスに対しメソッド内から上書き可能になります。

func (p *ByteSlice) Append(data []byte) {
    slice := *p
    // 本体部分は上とおなじだが、returnは除外
    *p = slice
}

実のところ、もう少し改善可能です。たとえば、この関数を標準的なWriteメソッドに合わせるように変更すると下のようになります。

func (p *ByteSlice) Write(data []byte) (n int, err os.Error) {
    slice := *p
    // これも上と同じ
    *p = slice
    return len(data), nil
}

このようにすることで*ByteSlice型は、利便性の高い標準インタフェースio.Writerを満たすようになり、下の例のようにスライスに対し出力することが可能となります。

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

上でByteSliceのアドレスを渡しているのは、*ByteSliceでなければio.Writerインタフェースを満たさないためです。レシーバとしてポインタまたは値のどちらを受け取るかによって、次の違いがあります。レシーバとして値を受け取るメソッドでは、ポインタまたは値で呼び出すことができますが、ポインタを受け取るメソッドでは、ポインタでしか呼び出すことができません。これは後者ではメソッド内でレシーバを変更可能であるためです。(複製された値に加えた変更は破棄されてしまうため。)

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