Go 1.22でforループの変数メモリ再利用が変更されたので挙動を確認する

Go 1.22がリリースされ、多くの改良が加えられました。
forループ内での変数メモリ再利用に関しても、変更があったので挙動を確認します。

変更の詳細と実際のコード例

Go 1.22より前のバージョンでは、for文の各ループで変数のメモリが再利用されていました。Go 1.22ではこの挙動が変更され、各ループで新しいメモリが確保されるようになりました。

package main
import (
	"fmt"
)
func main() {
	var funcs []func()
	for i := 0; i < 3; i++ {
		funcs = append(funcs, func() { fmt.Println(i) })
	}
	for _, f := range funcs {
		f()
	}
}

https://go.dev/play/p/dpi0ze_iQ2k?v=goprev
Go 1.21はすべての関数が同じ変数 i を参照しているため、出力はすべて3になります

3
3
3

https://go.dev/play/p/dpi0ze_iQ2k
Go 1.22 では各ループでiに新しいメモリが確保されるため、出力は以下のようになります

0
1
2

こちらのコードを1.21 と1.22でそれぞれベンチマークを取ってみます

package main

import (
	"testing"
)

func BenchmarkForloop(b *testing.B) {
	for n := 0; n < b.N; n++ {
		var funcs []func()

		for i := 0; i < 1000; i++ {
			funcs = append(funcs, func() {
				i = i
			})
		}

		for _, f := range funcs {
			f()
		}
	}
}

▼Go1.21の場合

$ go test -bench . -benchmem
goos: darwin
goarch: amd64
cpu: Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz
BenchmarkForloop-12    	   33302	     34164 ns/op	   41216 B/op	    1013 allocs/op
PASS
ok  	_/Users/ami/Documents/ami/go22	1.780s

▼Go1.22の場合

$ go test -bench . -benchmem
goos: darwin
goarch: amd64
cpu: Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz
BenchmarkForloop-12    	   22713	     52746 ns/op	   41536 B/op	    2012 allocs/op
PASS
ok  	_/Users/ami/Documents/ami/go22	2.070s

allocs/op(メモリ割り当ての回数)が1000ループ文確かに増えていますね!

さいごに

Go 1.22ではより直感的な動きになるので、コードの可読性も向上しそうです。
バージョンアップを行う際は挙動が変わる可能性があるので注意が必要です。
Go 1.22では大量のループを行う際にメモリ使用量が激増する場合があるので、そちらも注意が必要です。