仕事でGoを使うようになって1年ほどたったので、その中で若干気になっていることをまとめてみる。
ポインタの使いどころ
ポインタによる変数の受け渡しは値のコピーをせずアドレスを渡すだけですむが、ガベージコレクションに負荷がかかりおおくのCPU時間を消費する可能性があるらしい。
どういうときに使うのか
- 引数やレシーバを関数内で書き換える必要がある場合
- コピーを避けたいデータを引数、レシーバにする場合
- 大きな構造体や配列を扱う場合
- 大きな構造体をスライスに持たせる場合
3つ目、4つ目の大きな構造体の大きいの基準とは?と思っていたところ、ちゃんと記述があった。
構造体のフィールドの値すべてを引数に取る関数を考え、多いと感じるかどうかで考えるといいらしい。
Goにおけるポインタの使いどころ
golang の 引数、戻り値、レシーバをポインタにすべきか、値にすべきかの判断基準について迷っている - pospomeのプログラミング日記
値レシーバとポインタレシーバ
Goでは型に対してメソッドを定義できる。
そのメソッドを定義するときに出てくる概念がレシーバである。
レシーバの宣言方法には2つある。
それが値レシーバとポインタレシーバなのだが、どう使い分けるのがいいのか。
自分なりの結論としては、レシーバの値を書き換えるようなメソッドを定義するときはポインタレシーバを使う、である。
ただし、メソッドを呼び出すときにレシーバの値はコピーされることを考えると、自分で定義した構造体のメソッドを定義するときは原則ポインタレシーバで定義するとしてしまってもよさそう。
ひとつ前のポインタの使いどころで書いた構造体の大きさによる判断は正直わからないので計測して効率化するとかじゃない限りは考えないことにした。
さらに一歩
- なぜGoのerrorインターフェースはポインタレシーバで実装するべきなのか - Yappli Tech Blog
- Sliceのレシーバによって参照される値はコピーにはならない - チョキチョキかにさん
メソッドの引数における型
使う場面は限られているだろうが、どんな型の配列でも、スライスでも引数として受け取れる関数を作りたいとき、interface{}
を使うことで実現できる
試しに以下のようなコードを書いてみる。
package main
import (
"fmt"
"reflect"
)
func printElements(data interface{}) {
fmt.Println(data)
fmt.Println(reflect.TypeOf(data))
}
func main() {
fmt.Println("int型配列")
numbers := []int{2, 3, 5, 7, 11}
printElements(numbers)
fmt.Println("string型配列")
texts := []string{"foo", "bar", "baz", "qux", "quux"}
printElements(texts)
}
実行結果は以下のとおり、int型配列を引数に与えるとint型の配列が、string配列ならstring配列が関数内でそのまま使える。
int型配列
[2 3 5 7 11]
[]int
string型配列
[foo bar baz qux quux]
[]string
上で示したコード例において、引数の型を[]interface{}
としてしまうと、コンパイルエラーとなる。
[]interface{}
型の引数ではinterface{}
型の配列・スライスしか受け取れないらしい。
続いて、引数の個数や型がわからない時の書き方。
この場合は以下のように書ける。
package main
import (
"fmt"
"reflect"
)
func printElements(data ...int) {
fmt.Println(data)
fmt.Println(reflect.TypeOf(data))
}
func main() {
fmt.Println("int型配列")
numbers := []int{2, 3, 5, 7, 11}
printElements(numbers...)
// もしくは
printElements(1,2,3,4,5)
}
Go言語で配列・スライスをなんでも受け取れる関数を作る #interface - Qiita
Goのドット3つ - tom-256.log
Slice
【Go】サブスライスは元スライスとメモリを共有する #Make - Qiita
おまけ
参考にした記事の中であった以下のドキュメントはブックマークしておくとよさそう
日本語翻訳版はこっち