Go 1.22在2024年2月正式发布,几个改动虽然不算大但都挺实用,尤其是range over integers和路由增强,日常开发直接受益。
Range over integers
终于不用for i := 0; i < n; i++了。Go 1.22支持直接对整数range:
// 之前
for i := 0; i < 10; i++ {
fmt.Println(i)
}
// Go 1.22
for i := range 10 {
fmt.Println(i) // 0, 1, 2, ..., 9
}
range N产生从0到N-1的序列。看起来是小改动,但写起来确实舒服不少,尤其是嵌套循环或者简单的重复执行场景:
// 重试3次
for attempt := range 3 {
err := doSomething()
if err == nil {
break
}
log.Printf("attempt %d failed: %v", attempt+1, err)
time.Sleep(time.Second * time.Duration(attempt+1))
}
增强的HTTP路由
net/http的ServeMux终于支持方法匹配和路径参数了,不用为了这点功能引入第三方路由库:
mux := http.NewServeMux()
// 方法匹配:只响应GET请求
mux.HandleFunc("GET /api/users", listUsers)
// 路径参数:{id}会被捕获
mux.HandleFunc("GET /api/users/{id}", getUser)
// POST方法
mux.HandleFunc("POST /api/users", createUser)
// 通配符:匹配剩余路径
mux.HandleFunc("GET /files/{path...}", serveFile)
获取路径参数用r.PathValue():
func getUser(w http.ResponseWriter, r *http.Request) {
id := r.PathValue("id")
// ...
}
func serveFile(w http.ResponseWriter, r *http.Request) {
path := r.PathValue("path") // 匹配剩余的完整路径
// ...
}
路由优先级规则:更具体的模式优先。GET /api/users/{id}比GET /api/users/{name...}更具体。如果两个模式有冲突且无法确定优先级,注册时会panic。
对于中小项目来说,标准库的路由能力终于够用了。不过如果需要中间件链、路由分组这些功能,chi或gin还是更方便。
for循环变量语义修复
这是Go历史上最臭名昭著的坑之一。Go 1.22之前,for循环变量在每次迭代中共享同一个地址:
// Go 1.21及之前的坑
values := []int{1, 2, 3}
var funcs []func()
for _, v := range values {
funcs = append(funcs, func() {
fmt.Println(v) // 全部输出3!
})
}
for _, f := range funcs {
f()
}
这是因为闭包捕获的是变量v的地址,而v在循环结束后的值是3。以前的workaround是在循环里加一行v := v。
Go 1.22修复了这个问题,每次迭代的循环变量是独立的:
// Go 1.22
values := []int{1, 2, 3}
var funcs []func()
for _, v := range values {
funcs = append(funcs, func() {
fmt.Println(v) // 正确输出1, 2, 3
})
}
同样适用于goroutine场景:
for _, url := range urls {
go func() {
resp, err := http.Get(url) // Go 1.22: 每个goroutine拿到独立的url
// ...
}()
}
这个修改通过go.mod中的Go版本控制。只有go.mod声明go 1.22或更高版本时才启用新语义,老项目不受影响。
slices包增强
slices包在1.21引入,1.22新增了几个实用函数:
import "slices"
// Concat:拼接多个切片
s1 := []int{1, 2}
s2 := []int{3, 4}
s3 := []int{5}
result := slices.Concat(s1, s2, s3)
// [1, 2, 3, 4, 5]
// Delete/Replace的边界处理改进
s := []int{0, 1, 2, 3, 4}
s = slices.Delete(s, 1, 3) // 删除索引1到2的元素
// [0, 3, 4]
math/nand/v2
新版随机数包,API更简洁:
import "math/nand/v2"
// 直接用顶层函数,不需要seed
n := rand.IntN(100) // [0, 100)
f := rand.Float64() // [0.0, 1.0)
n32 := rand.N[int32](100) // 泛型版本
v2默认使用ChaCha8算法代替之前的Source,安全性更好。顶层函数自动使用随机seed,不需要手动rand.Seed(time.Now().UnixNano())了。
其他变化
- go vet增强:能检测出更多
defer中的常见错误 - go test -count:默认值从0改为1,避免测试缓存问题
- 编译器优化:PGO(Profile-Guided Optimization)改进,性能提升2-14%
- trace工具重写:
go tool trace完全重写,处理大trace文件不再OOM
总结
Go 1.22没有什么革命性的变化,但几个改进都很实在:range over integers减少样板代码,路由增强减少依赖,循环变量修复消除了一个历史坑。升级成本低,值得尽早切换。