🎗️

【Go】for rangeで要素の値が変更されない理由と対処方法

2023/01/16に公開

はじめに

Goのスライスなどをfor rangeループさせて、要素の値を変更しようとしたときに、コードの書き方によっては値が変更されない場合があります。
この記事では、その理由と対象方法についてまとめています。

値が変わらない理由

値が書き換わらないコードの例をつぎに示します。

func main() {
	s := []int{1, 2, 3}
	
	fmt.Println(s) // [1, 2, 3]

	for _, i := range s {
		i += 1
	}

	fmt.Println(s) // [1, 2, 3]
}

このコードは、スライスをfor rangeで回すときに値をインクリメントしていますが、ループを終えた後の結果は、変更前と同じままです。

この理由は、スライスの各要素と、for rangeで登場する変数のアドレスを見るとわかります。

func main() {
	s := []int{1, 2, 3}
	fmt.Println(s)

	for _, i := range s {
		i += 1
	}

	fmt.Println(s)

	fmt.Printf("index 0: %p\n", &s[0])
	fmt.Printf("index 1: %p\n", &s[1])
	fmt.Printf("index 2: %p\n", &s[2])

	for i, v := range s {
		fmt.Printf("Inside of loop, index %d: %p\n", i, &v)
		i += 1
	}

	fmt.Println(s)
}

上記のコード中のアドレスの出力結果は、つぎのようになります。

index 0: 0x140000180c0
index 1: 0x140000180c8
index 2: 0x140000180d0

Inside of loop, index 0: 0x1400001a0d0
Inside of loop, index 1: 0x1400001a0d0
Inside of loop, index 2: 0x1400001a0d0

ここから、スライスの各要素のアドレスとfor rangeの中で登場する値のアドレスが異なっていることがわかります。また、for range中では、ループごとに値のアドレスが変更されることはなく、同じアドレスを参照しています。

以上のことから、ループさせている変数の要素のアドレスと、for range内での変数のアドレスが異なるため、for range内で要素の値を書き換えたとしても、元の変数の要素の値は変更されないということになります。

対処方法

ここでは、for rangeで値が変更されるようにするためには、どのように書けばいいかの例を示します。

func main() {
	s := []int{1, 2, 3}
	fmt.Println(s)

	for i, _ := range s {
		s[i] += 1
	}

	fmt.Println(s) // [2, 3, 4]
}

理由の方で、for range中で登場する変数のアドレスとループさせている変数の要素のアドレスが異なっている場合に、要素の値が変更されないことを確認しました。
このコードでは、for rangeでインデックスを取得し、ループさせている変数の要素にアクセスして、値を書き換えています。

for rangeの部分は、つぎのようにも書けます。

for i := 0; i < len(s); i++ {
	s[i] += 1
}

Discussion