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

源码侧 `api/go1.22.txt` 有 135 条公开 API 增量；公开标准库目录新增 `go/version`、`iter`、`math/rand/v2`。

## 主要变化

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

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

```go
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，但有两类代码需要审查：

```go
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` 会产生 `0` 到 `9`。这个写法适合固定次数循环，不需要额外创建切片，也避免手写三段式 `for`。

```go
for i := range retries {
    if try(i) {
        break
    }
}
```

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

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

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

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

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

### 4. `math/rand/v2` 和 `go/version` 新增

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

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

## 语言与规范

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

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

循环变量变化和 `go.mod` 的 `go` 行有关。只有按 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.PathValue` 和 `SetPathValue` 可读写 ServeMux 通配符，测试 handler 时不必真的构造完整路由匹配流程。
- `database/sql`、`go/types`、`crypto/x509` 等包有小幅 API 增强。

## 小版本特殊变化

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

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

- `go1.22.1` 修复 `crypto/x509`、`html/template`、`net/http`、`net/http/cookiejar`、`net/mail` 安全问题，并修 trace 命令、`go/types`、`net/http`。
- `go1.22.2` 修复 `net/http` 安全问题，同时修 `encoding/gob`、`runtime/trace`。
- `go1.22.3` 修复 `go` 命令和 `net` 安全问题。
- `go1.22.4` 修复 `archive/zip` 和 `net/netip` 安全问题。
- `go1.22.7` 修复 `encoding/gob`、`go/build/constraint`、`go/parser` 安全问题，同时修 `go fix` 和 runtime。
- `go1.22.11` 修复 `crypto/x509` 和 `net/http` 安全问题。
- `go1.22.12` 修复 `crypto/elliptic` 安全问题，并修 compiler 和 `go` 命令。

## 参考

- [Go 1.22 Release Notes](https://go.dev/doc/go1.22)
- [Go Release History](https://go.dev/doc/devel/release)
- [Go 1.22 source tag](https://go.googlesource.com/go/+/refs/tags/go1.22.0)
- [api/go1.22.txt](https://go.googlesource.com/go/+/refs/tags/go1.22.0/api/go1.22.txt)

