📌

Go1.22のforループupdateに関する備忘録

2024/03/11に公開

今更ながら動かしてみたので備忘録

Go1.22が2/6にリリースされました

https://go.dev/doc/go1.22
いくつか更新はありますが、一番気になるのが以下の記載

Go 1.22 makes two changes to "for" loops.

  • Previously, the variables declared by a "for" loop were created once and updated by each iteration. In Go 1.22, each iteration of the loop creates new variables, to avoid accidental sharing bugs. The transition support tooling described in the proposal continues to work in the same way it did in Go 1.21.
  • "For" loops may now range over integers.

2点目は

for i := range 10 {
    fmt.Println(10 - i)
  }

のような記法ができるようになったよ!と具体例がありましたが1点目がいまいちよくわからない...
ループの各イテレーションが新しい変数を作ってくれるというのは文面から理解できますが、具体的にどのように挙動が変わるのでしょうか

実際にためしてみた

今回はGoの1.21.81.22.1を用意して比較してみました。
比較用に用意したコードは以下の通り

package main

import "fmt"

func main() {
	var funcs []func()
	for i, v := range []int{1, 2, 3, 4, 5} {
		funcs = append(funcs, func() {
			fmt.Println(i, v)
		})
	}
	for _, f := range funcs {
		f()
	}
}

1.22.1の場合

出力は以下の通り

0 1
1 2
2 3
3 4
4 5

ループの各イテレーションで代入された値がf()で出力されています。コードを見たときの想定挙動は多くの方がこちらではないかと思います。

1.21.8の場合

出力は以下の通り

4 5
4 5
4 5
4 5
4 5

最後のloopのi=4,v=5が上書きされてf()で出力されてしまいました。リリースノートに記載のあった通り、iとvは一度だけ作成されて上書きで使い回されています。
1.22.1と同様の結果を得るためには、for文の中でi:=iのように明示する必要があります。

package main

import "fmt"

func main() {
	var funcs []func()
	for i, v := range []int{1, 2, 3, 4, 5} {
		i := i
		v := v
		funcs = append(funcs, func() {
			fmt.Println(i, v)
		})
	}
	for _, f := range funcs {
		f()
	}
}
0 1
1 2
2 3
3 4
4 5

実例

Goの並列テストを行う際、これまでは以下のようにt:=tと再定義していましたがこれが不要になります。大きな変更ではないですが結構忘れるので嬉しい。

func TestHoge(t *testing.T) {
	t.Parallel()
	cases := []struct {
		name string
		req   int
		want bool
	}{
        // test内容
	}
	for _, tt := range cases {
        tt := tt
		t.Run(tt.name, func(t *testing.T) {
			t.Parallel()
			if got := Hoge(tt.req); tt.want != got {
			}
		})
	}
}

Discussion