⭐️Go: ポインタの使い所

Goのポインタの使い所について

引数やレシーバを関数内で書き換える必要がある場合

引数やレシーバを関数内で書き換える必要がある場合、書き換える対象はポインタで渡す必要がある。

逆に関数内でレシーバに変更を加えない関数は、値レシーバを使った方が「この関数はレシーバに変更を加えない」というのがシグネチャだけで明示的になるので、可読性の観点でも好ましい。

type S struct { value string }

func (s S) SetA (v string) {
  s.value = v
}

func (s *S) SetB (v string) {
  s.value = v
}

func main() {
  var s S
  s.SetA("a")
  fmt.Println(s.value) // sはゼロ値のまま
  s.SetB("b")
  fmt.Println(s.value) // b
}

コピーを避けたいデータを引数、レシーバにする場合

os.Filesync.Mutex などコピーが発生すると問題が生じるような構造体の場合は、ポインタで扱うことでコピーされないようにする。

// os.Open では File 型をポインタで返している
func Open(name string) (file *File, err error) {
  return OpenFile(name, O_RDONLY, 0)
}

大きな構造体や配列を扱う場合

  • 大きな構造体や配列を扱う場合はポインタを使う方が効率的
  • 逆にintstringなどのプリミティブな型やフィールドが多くない構造体に関しては、コピーのコストはあまり気にならず、むしろポインタで扱った方がGCの兼ね合いで非効率になるので、値レシーバにした方が良い

構造体の「大きい」の基準は Go Code Review Comments には以下のように書かれてる。

If the receiver is a large struct or array, a pointer receiver is more efficient. How large is large? Assume it’s equivalent to passing all its elements as arguments to the method. If that feels too large, it’s also too large for the receiver.

受け手が大きな構造体や配列である場合、ポインタを受け手として渡す方が効率的です。「大きい」とはどの程度でしょうか?そのすべての要素をメソッドの引数として渡すのと同じことだと考えてみてください。もしそれが大きすぎると感じるなら、受け手としても大きすぎるということです。

以下の記述もあるので、迷ったらポインタでもいいのかも。。

If in doubt, use a pointer,

注意点

ポインタによる変数の受け渡しは、値をコピーする必要がなくアドレスを渡すだけで完了するので効率的ですが、Goの場合ポインタを使いすぎるとガベージコレクションに負荷がかかってしまい、多くのCPU時間を消費するようになる可能性がある

参考資料