🐏

Golang, for rangeで値が更新されないとき

2022/03/06に公開約2,300字

前提

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}]}
}

play ground

まず、こちらを踏まえると、

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
}

のようにすると、取り出されるnumnum1, 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
}

のように、Numsnumの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
}

でも、更新される。なぜなら、取り出されるnumnum1, num2, num3, num4コピーではなく、num1, num2, num3, num4ポインタであるため。

Discussion

ログインするとコメントできます