構造体のフィールドにマップがある場合の挙動
構造体のフィールドにマップがある場合、構造体s1を代入でs2にコピーしたときに同じポインタを参照してしまうことへの対処法。(ホントは別々の構造体として扱いたい場合)
Go言語のマップは参照型
Go言語で構造体をコピーするとき、マップは参照型なので代入するとポインタがコピーされてしまう。
マップs2.mpをいじると元のマップs1.mpの値も書き換わってしまします。
(同じものを見ているからそれはそう)
package main
import (
"fmt"
)
type st struct {
i int
s string
b bool
sl []int
mp map[int]string
}
func main() {
s1 := st{i: 1,
s: "Go",
b: true,
sl: []int{1, 2, 3},
mp: map[int]string{1: "Go", 2: "Java", 3: "C#"},
}
fmt.Println(s1)
s2 := s1
s2.i = 10
s2.s = "Python"
s2.b = false
s2.sl = append(s2.sl, 10)
s2.mp[4] = "Haskell"
fmt.Println(s1)
fmt.Println(s2)
}
下記のようにs1.mp[4]にHaskellが入ってしまう。
{1 Go true [1 2 3] map[1:Go 2:Java 3:C#]}
{1 Go true [1 2 3] map[1:Go 2:Java 3:C# 4:Haskell]}
{10 Python false [1 2 3 10] map[1:Go 2:Java 3:C# 4:Haskell]}
解決策
別のポインタを確保して、loopで値を入れ直す。
package main
import (
"fmt"
)
type st struct {
i int
s string
b bool
sl []int
mp map[int]string
}
func (ss st) copy() st {
newS := ss
newS.mp = make(map[int]string)
for k, v := range ss.mp {
newS.mp[k] = v
}
return newS
}
func main() {
s1 := st{i: 1,
s: "Go",
b: true,
sl: []int{1, 2, 3},
mp: map[int]string{1: "Go", 2: "Java", 3: "C#"},
}
fmt.Println(s1)
s2 := s1.copy()
s2.i = 10
s2.s = "Python"
s2.b = false
s2.sl = append(s2.sl, 10)
s2.mp[4] = "Haskell"
fmt.Println(s1)
fmt.Println(s2)
}
{1 Go true [1 2 3] map[1:Go 2:Java 3:C#]}
{1 Go true [1 2 3] map[1:Go 2:Java 3:C#]}
{10 Python false [1 2 3 10] map[1:Go 2:Java 3:C# 4:Haskell]}
なぜスライスでは発生しないのか?
調べてみるとスライスもマップと同じ参照型。
なのになぜ同じように元の値が変わらないか。
この記事に答えがありました。
Go言語は、容量オーバしたスライスに対して新しくメモリ領域を確保してコピーします。
appendした際に容量オーバしたので、ar1とar2は、それぞれ別の領域を参照していることになります。
ということはスライスの容量確保によっては同じ現象が起きることになる。
のでスライスはcopyを使うか、空スライスにappendするのが無難。
どちらがベストプラクティスなのか分かっていないので誰かご教えて下さい!
公式wikiに書いてあると教えていただきました。
スライスも新しいスライスを作成してappendするのが一例だそうです!
ここまでのまとめplayground