🐏
Golang, for rangeで値が更新されないとき
前提
golangでfor range
を使って、値を取り出し、その値を更新しようとしたら更新されなかったので、for range
の挙動を調べました。
サンプルコード1
package main
import (
"log"
)
type numsList struct {
Nums []num
}
type num struct {
ID string
Num int
}
func main() {
num1 := num{ID: "a", Num: 1}
num2 := num{ID: "b", Num: 2}
num3 := num{ID: "c", Num: 3}
num4 := num{ID: "d", Num: 4}
nums := numsList{
Nums: []num{num1, num2, num3, num4},
}
for _, num := range nums.Nums {
log.Printf("%v", num)
}
// numsListを更新
for _, num := range nums.Nums {
num.Num += 1
}
log.Printf("%v", nums)
// 変化なし
// {[{a 1} {b 2} {c 3} {d 4}]}
// numsListを更新
for i := range nums.Nums {
nums.Nums[i].Num += 1
}
log.Printf("%v", nums)
// 1加算されている
// {[{a 2} {b 3} {c 4} {d 5}]}
}
まず、こちらを踏まえると、
When ranging over a slice, two values are returned for each iteration. The first is the index, and the second is a copy of the element at that index.
for _, num := range nums.Nums {
num.Num += 1
}
のようにすると、取り出されるnum
はnum1
, num2
, num3
, num4
のコピーであるので、コピーに対してnum.Num += 1
のようにしても元々のnum1
, num2
, num3
, num4
は影響を受けない。よって、更新されない。
一方、
for i := range nums.Nums {
nums.Nums[i].Num += 1
}
のように、インデックスi
で直接アクセスすると、更新される。
このような、挙動になるのは、
type numsList struct {
Nums []num
}
のように、Nums
をnum
のsliceで定義しているからであり、
type numsList struct {
Nums []*num
}
のように定義すると、
サンプルコード2
package main
import (
"log"
)
type numsList struct {
Nums []*num
}
type num struct {
ID string
Num int
}
func main() {
num1 := num{ID: "a", Num: 1}
num2 := num{ID: "b", Num: 2}
num3 := num{ID: "c", Num: 3}
num4 := num{ID: "d", Num: 4}
nums := numsList{
Nums: []*num{&num1, &num2, &num3, &num4},
}
for _, num := range nums.Nums {
log.Printf("%v", num)
}
// &{a 1}
// &{b 2}
// &{c 3}
// &{d 4}
// numsListを更新
for _, num := range nums.Nums {
num.Num += 1
}
for _, num := range nums.Nums {
log.Printf("%v", num)
}
// 1加算されている
// &{a 2}
// &{b 3}
// &{c 4}
// &{d 5}
}
play ground
のように、
for _, num := range nums.Nums {
num.Num += 1
}
でも、更新される。なぜなら、取り出されるnum
はnum1
, num2
, num3
, num4
のコピーではなく、num1
, num2
, num3
, num4
のポインタであるため。
Discussion