問題の歴史:
Goは明示的な値セマンティクスを持つ言語として設計されました。ほぼすべてが値でコピーされますが、ポインタやスライスは例外です。これは推論を簡素化し、安全性を向上させましたが、「罠」がいくつか存在します。
問題:
多くの開発者は、関数に構造体を渡すと、その変更が「外部」にも反映されると期待します。しかし、すべての内容がコピーされます(入れ子になったフィールドを含む — 値で!)。スライスやマップは異なる挙動をし、「コンテナ」はコピーされますが「内容」はコピーされません。
解決策:
変更が期待される場合は、大きな構造体をポインタで渡します。スライスの場合、コピーされるのはディスクリプタ(長さ、容量、ポインタ)だけで、内容はコピーされません。元のスライスの変更(インデックス経由)は外部に反映されます。構造体の場合は、すべてがコピーされます:
type Point struct { X, Y int } func move(p Point) { p.X = 100 } func movePtr(p *Point) { p.X = 100 } func demo() { pt := Point{10, 10} move(pt) fmt.Println(pt.X) // 10 movePtr(&pt) fmt.Println(pt.X) // 100 }
重要な特徴:
関数内で構造体を変更すると、オリジナルは変更されますか?
いいえ、構造体が値で渡された場合、変更はローカルです。
type User struct {Name string} func f(u User) {u.Name = "Ann"}
関数内でスライスの要素を変更すると、オリジナルは変更されますか?
はい。スライスは共通の配列への「ビュー」です。要素を変更すれば、元のデータも変更されます。
func f(s []int) {s[0] = 99}
関数内で作成したスライスを返すとどうなりますか?
スライスの「ヘッダー」自体はコピーされますが、裏側の配列にはアクセス可能です。外部で参照を保持しない場合、データはGCによって回収される可能性があります。
関数内でUser構造体の処理が値で行われ、変更が戻ってこなかったため、バグを追跡するのが難しかった。
利点:
欠点:
大きな構造体は明示的にポインタで渡され、スライスの場合は常にその挙動がコメントされたり確認されたりします。混乱がなく、すべてが値セマンティクスを予期します。
利点:
欠点: