Goでは、構造体(struct)はデフォルトで値で渡され、返されます。つまり、関数を呼び出すときや返すときに構造体全体がコピーされます。小さな構造体の場合は透明性がありますが、大きな構造体にとっては重要な問題です。
初期のGoは少ないアロケーションで効率よく動作することを目指していました。しかし、大きなデータの無意識のコピーの危険は、構造体が多くのフィールドやネストされたオブジェクトを使用するようになると現れました。このような操作の性能が損なわれることがあります。違いはプロファイリングやGCの苦痛でのみ明らかになることもあります。
もし構造体が大きなサイズを持つと、関数の各呼び出し、返却、または代入のたびにコピーすることがオーバーヘッドになります。これにより、以下のような問題が発生します:
大きな構造体の場合は、構造体自体ではなく、そのポインタ(*T)を渡し、返すことが推奨されます。これにより、オーバーヘッドが減少し、データの単一のインスタンスで作業できます。
コード例:
package main import "fmt" type Large struct { Data [1024]int } // 値での受け渡し(大きなオブジェクトには不適切) func ValueProcess(l Large) { l.Data[0] = 123 // コピーのみが変更される } // ポインタでの受け渡し func PointerProcess(l *Large) { l.Data[0] = 456 // オリジナルが変更される } func main() { a := Large{} ValueProcess(a) fmt.Println("ValueProcessの後:", a.Data[0]) // 0 PointerProcess(&a) fmt.Println("PointerProcessの後:", a.Data[0]) // 456 }
主な特徴:
1. Goの関数からローカルな構造体変数のポインタを返すことはできますか?
はい。Goはそのようなポインタの有効性を保証しており、返されるポインタが指す値を自動的にヒープに移動します(ヒープへのエスケープ)。
func NewLarge() *Large { l := Large{} return &l }
2. 構造体を値で関数に渡し、内部のフィールドを変更した場合、オリジナルが変更されますか?
いいえ:変更されるのはコピーのみで、関数外のオリジナルはそのままです。
3. 常に構造体のポインタを使用するべきですか?
いいえ。小さな(数フィールドの)構造体に対しては、値での受け渡しが安全でしばしば好ましい(不変/値セマンティクス)、アロケーションを節約し、GCへの負荷を軽減します。
ロギングサービスでは、各イベントが大きな構造体を表し、値で関数から返されていました — 各変更が構造体全体をコピーしていました。
利点:
欠点:
ポインタで構造体を受け渡し、func(l *Large)やfunc() *Largeのような型シグネチャを通じてデータを変更することに決めました。
利点:
欠点: