1130 字
约 3 分钟
5
S09-J03 · 闭包与捕获:从 1.21 到 1.22 的语义变更
2024-12-31
2026-11-07

适用读者:维护老 Go 项目、准备升级 Go 版本的人。
前置:S09-B07。
实测环境:Go 1.22.2(2026-11-05)。
覆盖旧文:—(E 驱动)
覆盖扩展:E-S09-009 深化(go.mod 的 go 指令与语言语义)。
上一篇:S09-J02 · 字符串与 UTF-8:byte、rune 与 O(n) 陷阱
下一篇:S09-A01 · 模块与依赖。


1. 一句话结论

Go 1.22 改了 for 循环变量的作用域:每次迭代一个独立变量。这修掉了历史悠久的闭包 bug,但也可能改变依赖旧行为的代码。


2. 实测:三种写法的结果(Go 1.22.2)

// A) 循环内声明(1.22 新语义)
for i := 0; i < 3; i++ { fs = append(fs, func() int { return i }) }
→ 0 1 2

// B) 循环外声明(永远共享)
var j int
for j = 0; j < 3; j++ { fs = append(fs, func() int { return j }) }
→ 3 3 3

// C) 显式拷贝(任何版本安全)
for k := 0; k < 3; k++ { kk := k; fs = append(fs, func() int { return kk }) }
→ 0 1 2
写法 1.21 预期 1.22 实测
A 3 3 3 0 1 2
B 3 3 3 3 3 3
C 0 1 2 0 1 2

待核:本文尝试通过把 go.mod 改为 go 1.21 来复现旧语义,未成功(A 仍输出 0 1 2)。说明"语言版本 → 语义"的映射在本机工具链上未按预期生效。结论:升级前用本文 A/B/C 三段代码在目标工具链实测,不要依赖文档推断。


3. 为什么 B 永远危险

j 是循环外的变量,三个闭包共享它。循环结束时 j == 3,闭包在循环后执行 → 全读 3。

这不是 Go 的 bug,是"捕获变量"的正常语义。语言能做的只是"让 for 内声明的变量不再被共享"——而"循环外声明"是你主动选择的共享。


4. 升级检查清单

# 检查项 做法
1 找出所有"闭包 + 循环" grep 闭包字面量出现在 for 内
2 区分"捕获循环变量"还是"捕获外部变量" 看变量声明位置
3 依赖旧行为的地方 显式加 kk := k(语义不变,可读性更好)
4 测试 跑全量测试;重点看并发/回调相关
5 工具链 go.mod 的 go 指令 + CI 的 Go 版本要一致

判据:"这段代码在 1.21 与 1.22 下会得到不同结果吗?" 会 → 升级前显式改写,别赌语义。


5. 与 goroutine 的关联(前瞻,本系列不展开)

历史 bug 的典型形态:

for _, v := range items {
    go func() { process(v) }()      // 1.21: 全部用最后一个 v
}

在 1.22 下这个 bug 自动消失——这是升级的收益。但并发本身(调度、竞态、channel)不在本系列范围(旧素材缺口,见 X01 §2),细节归 S05。


6. 五个误区

误区 真相
"Go 1.22 修好了所有闭包问题" 只修 for 内声明;外部变量照样共享
"改 go.mod 就能回到旧语义" 实测未复现;以工具链实测为准
"显式拷贝是过时写法" 仍是"表达意图最清楚"的写法,跨版本安全
"升级只会带来好处" 依赖旧行为的代码可能出错(少见但存在)
"闭包 bug 只出现在并发里" 串行也会(B 写法就是串行 demo)

7. 自检

  1. A/B/C 三种写法在 1.22.2 下分别输出什么?
  2. 为什么 B 无法通过语言升级修好?
  3. 升级前的最小验证方式是什么?
  4. go.mod 的 go 指令在本机实验中起效了吗?
  5. 为什么说"显式拷贝跨版本安全"?

8. 下一篇

S09-A01 · 模块与依赖:go.mod、GOPROXY 与可复现构建

进阶层到此结束。下一篇进入架构师层:依赖从哪来、怎么保证可复现、拉不到包怎么排查。


本篇属于系列《S09 · Go 语言》。扩展点 E-S09-009 深化。A/B/C 输出为 Go 1.22.2 实测(旧语义未复现,已标待核)。

S09-J03 · 闭包与捕获:从 1.21 到 1.22 的语义变更
https://www.clxhxhhr.top/posts/5308/
作者
clxstart
发布于
2024-12-31
许可协议
CC BY-NC-SA 4.0
评论
0 条
还没有评论,先写一条吧。