Go 1.22 的 for range 循环变量不再复用,每次迭代的 v 是独立变量,闭包可直接捕获正确值,但 &v 地址每次不同且生命周期仅限当次迭代,取地址后长期持有需显式分配内存。

Go 1.22 的 for range 循环变量不再复用,不是“修好了旧 bug”,而是语义变了——闭包里直接用 v 不再需要手动传参或额外赋值,但你得清楚它现在为什么“看起来能用”,以及哪些地方依然会翻车。
for _, v := range items 中的 v 现在每次都是新变量
Go 1.22 起,v 在每次迭代中被编译器当作独立声明的变量处理,等价于隐式写了 v := item。这意味着:
- 闭包捕获
v时,实际捕获的是当前迭代的独立副本,不再是共享地址 -
go func() { fmt.Println(v) }()会大概率输出正确值(如a b c),但顺序仍由调度决定,不能保证 - 旧版靠
v := v或传参绕过问题的代码,在 Go 1.22 下仍可运行,但已非必需 - 这种行为对
for i := range items和for i, v := range items同样生效,i也是每次新建
&v 取地址行为彻底改变,老逻辑会崩
这是最容易被忽略、也最危险的点:以前 &v 总是返回同一地址,现在每次迭代都返回不同地址,且这些地址指向的内存可能在循环结束后失效。
- 若你把
&v存进切片或 map,比如ptrs = append(ptrs, &v),Go 1.22 下得到的是多个有效地址,但它们指向的变量生命周期只到本次迭代结束 → 后续解引用是未定义行为 - 用
&v当 map key(如m[&v] = "x"):Go 1.21 中 map 只有 1 个 key;Go 1.22 中变成多个 key,但所有指针最终可能都悬空 - 想长期持有地址?必须显式分配空间,例如
vs := make([]T, len(items)); for i, item := range items { vs[i] = item; ptr := &vs[i] }
整数范围遍历:for i := range 5 是合法语法
Go 1.22 新增支持对无类型整数字面量做 range,语义等价于遍历 [0, n) 的整数序列。
-
for i := range 5相当于for i := 0; i ,类型为 <code>int - 不支持带符号或浮点字面量:
range -5、range 3.14编译报错 - 该特性仅适用于常量整数,变量不行:
n := 5; for i := range n❌ - 底层仍生成传统 for 循环,无性能损失,但注意它不提供元素值,只给索引
别依赖“现在能跑”就跳过重构
很多旧代码在 Go 1.22 下看似正常了,其实是侥幸——尤其当逻辑隐含依赖变量地址稳定性时(比如对象池索引、调试标记、unsafe.Pointer 转换),行为可能从“总错”变成“偶尔错”或“错得更隐蔽”。
- 检查所有
&v使用点,尤其是存入全局结构、传给 C 函数、或用于sync.Pool的场景 - 避免在循环体内启动 goroutine 后还修改
v(虽然变量独立,但数据竞争风险仍在) - 用
go vet检查:新版 vet 对defer time.Since(t)等新增警告,但对循环变量地址问题无提示,得人工扫


















