ᕕ( ᐛ )ᕗ Jimyag's Blog

Go 1.22 新特性解析:循环变量语义与 ServeMux 路由

· 353 words · ~ 2 min read

Last modified:

Go 1.22 发布于 2024-02-06。这一版最值得关注的是 for 循环变量语义变化,它修掉了 Go 里最常见的闭包踩坑之一。

源码侧 api/go1.22.txt 有 135 条公开 API 增量;公开标准库目录新增 go/versionitermath/rand/v2

主要变化

1. for 循环变量按迭代独立创建

过去闭包捕获循环变量时,经常会捕获到同一个变量,导致 goroutine、回调或测试用例里读到最后一次迭代的值。

1
2
3
4
5
for _, tc := range tests {
    t.Run(tc.name, func(t *testing.T) {
        check(t, tc)
    })
}

在旧语义里,tc 是循环复用的变量,闭包看到的可能不是当前迭代那一份。Go 1.22 改成每次迭代创建新变量,代码更符合直觉。这个变化也影响 go vet:当文件按 Go 1.22 或更新语义分析时,vet 不再把这类捕获统一当成风险。

行为变化注意

这个语义由模块或文件的 Go 版本控制。按 Go 1.22 语义编译时,for 循环变量是 per-iteration;按旧语义编译时,仍然是 per-loop。绝大多数代码会因此少一个 bug,但有两类代码需要审查:

1
2
3
4
var ps []*int
for i := 0; i < 3; i++ {
    ps = append(ps, &i)
}

旧语义下,ps 里的指针可能都指向同一个 i;新语义下,每次迭代的 i 都是不同变量。依赖“地址相同”或“闭包看到后续迭代写入”的代码,本身就很脆弱,但行为确实会变。

另一类是测试和 goroutine。过去常见的 tc := tc 手动拷贝在新语义下通常不再需要,但保留也无害。读旧代码时要先看它按哪个 Go 版本语义编译,再判断闭包捕获是否有问题。

2. for range 可以遍历整数

for i := range 10 会产生 09。这个写法适合固定次数循环,不需要额外创建切片,也避免手写三段式 for

1
2
3
4
5
for i := range retries {
    if try(i) {
        break
    }
}

这里的重点不是少写几个字符,而是把“遍历一个范围”统一到 range 语法下。它和后续函数迭代器能力一起,让 Go 的迭代模型更集中。

3. net/http.ServeMux 路由能力增强

新的 ServeMux 模式可以表达 HTTP 方法、通配符和更精确的路径匹配。

1
2
3
mux := http.NewServeMux()
mux.HandleFunc("GET /posts/{id}", getPost)
mux.HandleFunc("POST /posts", createPost)

请求匹配后,可以通过 r.PathValue("id") 读取通配符值。这样很多小型 API 不再需要额外 router 才能表达“方法 + 路径参数”。标准库还定义了更明确的冲突规则:如果两个模式都能匹配同一个请求,但没有一个比另一个更具体,就会在注册时报错,避免运行时出现模糊路由。

4. math/rand/v2go/version 新增

math/rand/v2 是新的随机数 API,修正旧包里一些长期设计包袱。它提供新的源和生成器类型,API 也更明确地区分全局随机数和显式随机数源。对需要可复现随机序列的测试或模拟程序,显式 source 更容易控制。

go/version 提供 Go 版本字符串处理能力。工具、生成器和静态检查器经常要比较 go1.20go1.21.3go1.22rc1 这类字符串;自己用字符串排序容易错,标准库 API 可以按 Go 官方规则处理。

语言与规范

Go 1.22 对 for 做了两个变化。第一,循环变量按迭代独立创建,闭包捕获循环变量时不再默认共享同一个变量。第二,可以写 for i := range 10 这类整数 range。

此外,GOEXPERIMENT=rangefunc 提供了 range-over-function iterators 的预览,为 Go 1.23 的正式迭代器能力铺路。

循环变量变化和 go.modgo 行有关。只有按 Go 1.22 语义编译的包才使用新规则,因此同一工作区里不同模块可以按自己的 go 行得到对应语义。这是 Go 1.21 后“语言语义绑定到模块版本”的一个具体例子。

工具链与运行时

workspace 可以生成和使用 workspace 级 vendor 目录。go work vendor 会把工作区中多个模块的依赖统一写入 vendor 树,适合多模块工程使用同一份依赖快照。

go test -cover 对没有测试文件但有可执行语句的包会报告 0% 覆盖率,而不是简单显示 no test files。这个行为让覆盖率统计更符合直觉:有代码但没有被测试覆盖,就应该进入总体覆盖率计算。

trace UI 刷新,vet 也根据新的循环变量语义调整了闭包捕获检查,并新增 append 空追加、time.Since defer 等检查。

标准库与新增包

新增公开包:

  • math/rand/v2:新的随机数 API。
  • go/version:解析和比较 Go 版本字符串。
  • iter:源码树中新增的迭代器相关包,与 range-over-function 过渡相关。

重要变化:

  • net/http.ServeMux 支持方法、通配符和更精确的模式匹配。
  • net/http.Request.PathValueSetPathValue 可读写 ServeMux 通配符,测试 handler 时不必真的构造完整路由匹配流程。
  • database/sqlgo/typescrypto/x509 等包有小幅 API 增强。

小版本特殊变化

Go 1.22 系列共有 12 个小版本。循环变量语义和 ServeMux 新规则发布后,小版本修复集中在 compiler、go 命令、runtime、trace、net/http 和解析器。

Go 1.22 的安全修复和它的新功能有一条暗线:ServeMux 路由增强之后,net/http 的路径、方法、通配符和头部处理更关键;go/build/constraintgo/parser 的安全修复说明构建约束和源码解析也可能成为攻击面,例如工具处理不可信源码或生成代码时。encoding/gob 是二进制反序列化入口,crypto/x509crypto/elliptic 继续对应证书和椭圆曲线运算,net/netip 则影响网络地址判断。

  • go1.22.1 修复 crypto/x509html/templatenet/httpnet/http/cookiejarnet/mail 安全问题,并修 trace 命令、go/typesnet/http
  • go1.22.2 修复 net/http 安全问题,同时修 encoding/gobruntime/trace
  • go1.22.3 修复 go 命令和 net 安全问题。
  • go1.22.4 修复 archive/zipnet/netip 安全问题。
  • go1.22.7 修复 encoding/gobgo/build/constraintgo/parser 安全问题,同时修 go fix 和 runtime。
  • go1.22.11 修复 crypto/x509net/http 安全问题。
  • go1.22.12 修复 crypto/elliptic 安全问题,并修 compiler 和 go 命令。

参考

#Go #Golang #Go Release Notes #Go for Range #Net/Http