Goの参照渡しについて調べてみた
Goでは全てが値渡し
Goにおける参照渡し=ポインタの値渡し
Goでは関数にパラメータを渡すとき、全て値渡しで実現されています。
(C派生の言語はすべてそうらしいです)
じゃあ、参照渡しって何?ってなりますよね。
参照渡し=ポインタの値渡しです。
つまり、ポインタそのものを渡しているわけではなく、ポインタのコピーを渡しています。
値渡しと参照渡しの差は、内部の値をコピーするかどうかです。
こちらについては後ほど例を交えて説明します。
今回の内容はGo公式ドキュメントの『Pointers and Allocation』
の章に
詳細な記載があります。
本記事では、『Pointers and Allocation』 から要点を抜粋して紹介します。
値渡しと参照渡しの違いは内部値のコピー有無
まずは、先述した
値渡しと参照渡しの差は、内部の値をコピーするかどうかです。
について詳しく見ていきます。
公式ドキュメント『When are function parameters passed by value?』 の節に以下の記述があります。
For instance, passing an int value to a function makes a copy of the int, and passing a pointer value makes a copy of the pointer, but not the data it points to.
たとえば、int値を関数に渡すとintのコピーが作成され、ポインター値を渡すとポインターのコピーが作成されますが、ポインターが指すデータは作成されません。
つまり、
- 値渡し:値のコピーが作成される
- 参照渡し:ポインタのコピーは作成されるが、ポインタが指すデータ(値)のコピーは作成しない
といった差があります。
図にすると以下のとおりです。
同じ色の箱はアドレスが同じだと考えてください。
(図が下手なところはほっといてあげてください🙇♂️)
大きな差がありますね。
この差により、例えば、多くのフィールドを持つ構造体を関数の引数やレシーバとして渡す場合、
値渡しでは全フィールドのコピーが行われてしまうため
パフォーマンス的に良くないといった違いが生まれてきます。
無駄なコピーを行わないために全て参照渡しにしとけばいいか、
というとそれはまた別で考慮すべき点が出てきます。
「値渡し または 参照渡しのどちらを使用するか」については多くの議論がなされています。
- Go公式ドキュメント『Should I define methods on values or pointers?』
- Yury Pitsishin『Pass by pointer vs pass by value in Go』
- pospomeのプログラミング日記『golang の 引数、戻り値、レシーバをポインタにすべきか、値にすべきかの判断基準について迷っている』
- THE Finatext Tech Blog『Go言語(golang)における値渡しとポインタ渡しのパフォーマンス影響について』
コードで確認
では、最後にここまでの内容をコードで確認して終わります。
上記のplaygroundを実行すると、以下のように出力されました。
(アドレス部分は実行ごとに異なります)
main()における構造体のアドレス: 0xc00010a040
main()におけるnameのアドレス: 0xc00010a040
PassByReference()における構造体のアドレス: 0xc000102020
PassByReference()におけるnameのアドレス: 0xc00010a040
PassByValue()における構造体のアドレス: 0xc00010a050
PassByValue()におけるnameのアドレス: 0xc00010a050
PassByReference()
がレシーバを参照渡しで受け取る関数でPassByValue()
がレシーバを値渡しで受け取る関数です。
main()
とPassByReference()
を比較すると、両者の構造体のアドレスが異なっています。Human
構造体がもつname
フィールドについては同じアドレスを指しています。
つまり、レシーバのポインタはコピーしたものを参照していますが、
フィールドの値はコピーではなく、main()
で定義したものが使用されています。
先述の内容と一致しますね。
一方で、main()
とPassByValue()
を比較すると、
両者の構造体のアドレスおよびフィールドのアドレスがすべて異なります。
すなわち、レシーバのポインタおよびフィールドのすべての値に関して、コピーしたものを参照しています。
こちらも先述の内容と一致します。
納得!
ちなみに、なぜすべて値渡しで実現しているかについては、
先程紹介した下記の記事で触れられています。
Yury Pitsishin『Pass by pointer vs pass by value in Go』
→「Passing by value often is cheaper」の章