読者です 読者をやめる 読者になる 読者になる

はざまブログ

cybozu.com の中の人の個人ブログ

Go のポインタメソッドとバリューメソッドのどちらが良いのか

TL; DR
ほとんどの場合ポインタメソッドにしておけば良い。

Go のメソッドは、レシーバーとして値(value)を受け取るかポインタを受け取るか選択できます。

type Hoge struct {
   ...
}

// value receiver
func (h Hoge) Method() {}

// pointer receiver
func (h *Hoge) Method() {}

バリューメソッドでは、Hoge がコピーされます。ポインタメソッドではされません。

バリューメソッドであれば元のオブジェクトは書き換えられることがありませんが、ポインタメソッドは元のオブジェクトが書き換えられるかもしれません。とはいえ、そんなことよりはコピーのオーバーヘッドを気にしてポインタメソッドにしたくなることが多いでしょう。

ところが、Effective Go を読んでいるとこんな記述が出てきます。

The rule about pointers vs. values for receivers is that value methods can be invoked on pointers and values, but pointer methods can only be invoked on pointers.

おっと。ポインタメソッドには何やら制限があるようです。オブジェクトのポインタからしか呼び出せない??

実はこの文章の後に長めの解説があるのですが、読み飛ばしていると「バリューメソッドのほうが値・ポインタどちらからも使えるしいいのかも...」と考えてしまうかもしれません。後の文章はこう続きます。

This rule arises because pointer methods can modify the receiver; invoking them on a value would cause the method to receive a copy of the value, so any modifications would be discarded. The language therefore disallows this mistake. There is a handy exception, though. When the value is addressable, the language takes care of the common case of invoking a pointer method on a value by inserting the address operator automatically. In our example, the variable b is addressable, so we can call its Write method with just b.Write. The compiler will rewrite that to (&b).Write for us.

長いですね。実は大事なのは最後の一文で、コンパイラが適宜ポインタに書き換えてくれるからほとんどの場合ポインタメソッドも値・ポインタどちらからも呼び出せるようになっているのです。

どういう条件かというと、式が addressable な場合だそうです。ざっくり言えば、変数なら問題ありません。

具体的には net.IPNet#Contains はポインタメソッドなので、ポインタからしか呼び出せません。が、以下の例ではコンパイラが書き換えてくれるので値から呼び出せます。

v := net.IPNet{net.ParseIP("10.0.0.0"), net.CIDRMask(20, 32)}
v.Contains(net.ParseIP("10.0.0.2")) // OK

が、以下の場合は書き換えてくれないのでエラーになります。

net.IPNet{net.ParseIP("10.0.0.0"), net.CIDRMask(20, 32)}.Contains(net.ParseIP("10.0.0.2"))

というわけで、変数の場合はポインタメソッドでも問題なく呼び出せるのでした。