【Golang】関数のレシーバータイプの決め方

0

2022年04月22日 14:13

こんにちは、ISSUEの寒河江です。

関数のレシーバーは値か、ポインターにするべきか明確に理解できていなかったので、公式ブログ等を参考に改めてまとめてみました。

値型とポインター型のレシーバー

まずは本題に入る前に簡単に2つのレシーバータイプについてご説明します。

値型

レシーバーがポインターではないタイプです。下記Readの関数のようなレシーバです。vはコピーされた構造体でgetterとして使われることが多いです。
img

ポインター型

レシーバーがポインターのタイプです。下記Write関数のようなレシーバです。vはアドレス参照なのでv.dataの値にはstrがセットされます。値型では構造帯がコピーされているのでレシーバーの値が変わることはありません。このようなsetterとしての使われ方と、構造体をコピーしないので効率的なプログラムになります。
img

上記2つのレシーバータイプがあり、ここからこの2つの使い分けについて書いていきます!

基本的な考え方

レシーバータイプの分け方は値とポインターの特性の違いによると思いました。

ポインタータイプ→ポインタ渡しでオブジェクトの値を変更したい場合、またはレシーバーをコピーするコストが大きいとき

→小さなオブジェクトでコピーコストが小さいとき、値の変更が必要ない時(読み取り専用)

やや例外はあるもののレシーバータイプの決め方は上記が基準になりそうです。

値型を使う場合

参照型のような値であるmap、func、chan、sliceの場合

参照型データの場合は、値のままでも値を書き換えることができるのでポインター型にする必要がありません。

レシーバーが変更する必要のある構造体の場合

先ほどのsetterの話と同様です。値の場合、構造体のコピーが渡されてきているのでレシーバーの値を書き換えることはできません。下記PointerWriteのようにレシーバーがポインターの場合、S構造体のdataの値を書き換えることができます。
img

出力
img
サンプル

レシーバーがsync.Mutexのような同期的なフィールドの場合

標準ライブラリに組み込まれているsync.Mutexはコピーされてしまうと挙動がおかしくなります。下記valueMuxのようにsync.Mutexを含むstructを値レしバーにするとgo vetでも指摘してくれます。
img
img

レシーバーが大きな構造体または配列の場合

レシーバーの値が大きいものはポインターにして構造体をコピーするコストを抑えるのが効果的です。大きいの基準は引数に取った時に大きいと感じるものだそうです。大体の構造体は大きいと感じるので基本的にはレシーバーで問題なさそうですね。

レシーバーの値を変更したい時

先ほど値タイプとポインタータイプのレシーバの違いで説明した内容になります。ポインターレシーバーで呼び出した場合、レシーバーの値を変更することができます。値タイプの場合はレシーバをコピーしているのでレシーバーにstructの変更は反映されません。
img
出力
img

プリミティブな値の場合は値レシーバー

stringやint、小さな構造体、配列のようなプリミティブな値の場合は値レシーバーを利用します。理由は値レシーバーを関数内に渡す場合ヒープに割り当てる代わりにスタックのコピーを利用できるためガベージの量が減る可能性があるとのこと。
(この辺りはpospomeさんの記事を参考にしています。)

time.Timeのような不変型

time.Time型は以下の理由から不変型です。

・フィールドがすべてプライベート
・レシーバは Unmarshal 系を除きすべて Time 型(値型)
・オブジェクトを返すメソッドの戻り値もすべて Time 型(値型)

引用: https://skatsuta.github.io/2015/12/29/value-receiver-pointer-receiver/

一度定義されれば不変の値となるので、値を変更するためにポインターにする必要がないとのことです。

迷ったらポインタータイプにする

レシーバーのタイプが明確に判別できない場合は、ポインター型が推奨されているようです。値が書き変わるリスクはありそうな気がしますが、値レシーバー利用によるコピーのコストを考慮しているのかと思いました。

以上、関数のレシーバータイプの決め方でした!

参考

Receivers and Interfaces
Pointers vs. Values
Receiver Type
pospomeのプログラミング日記
Go言語のスタックとヒープ

# Go
0

診断を受けるとあなたの現在の業務委託単価を算出します。今後副業やフリーランスで単価を交渉する際の参考になります。また次の単価レンジに到達するためのヒントも確認できます。

目次を見る