为什么这篇文章同时涵盖四个次要版本
Go 每六个月发布一次。连续阅读四套发布说明不是任何人想要度过下午的方式。但如果你在生产环境中运行 Go,你需要知道发生了什么变化。
本文涵盖 Go 1.22 到 Go 1.26 — 2024 年初到 2026 年初发布的版本。这是两年的语言演进,放在一篇文章里。
Go 1.22: For 循环改变了一切
2024 年 2 月。Go 1.22 中最有影响力的变化不是新包或编译器优化。而是修复了 for 循环处理变量的方式。
在 Go 1.22 之前,这段代码有一个微妙的 bug:
var funcs []func()
for i := range 10 {
funcs = append(funcs, func() {
fmt.Println(i)
})
}
for _, f := range funcs {
f() // 打印 9 次 9,而不是 0-9
}
每个闭包捕获相同的变量 i。当你调用这些函数时,i 是 9。每个 Go 开发者至少踩过这个坑一次。
Go 1.22 改变了这一点。 每次循环迭代现在都创建新变量。闭包捕获不同的变量。代码的行为符合你的预期 — 打印 0 到 9。
Go 团队提供了过渡工具,以便你可以检测依赖于旧行为的代码。go vet 可以捕获该模式。但修复本身很干净:如果你的代码看起来正确,它很可能就是正确的。
整数范围遍历 是另一个值得注意的添加:
for i := range 10 {
fmt.Println(10 - i)
}
简单、明显,消除了一种常见模式。for i := 0; i < n; i++ 不再被需要。
Go 1.26: 自引用泛型修复
2026 年 2 月。最新稳定版本,对任何构建泛型数据结构的人来说最重要的变化。
type Tree[T any] struct {
Value T
Left *Tree[T]
Right *Tree[T]
}
在 Go 1.26 之前,这是非法的。泛型类型不能在其类型参数列表中引用自身。你必须使用非泛型节点类型或接口技巧来解决问题。
Go 1.26 解除了这个限制。泛型类型现在可以引用自身,这意味着链表、树和图节点可以成为真正的泛型,而无需包装类型。
new() 内置函数也变得更灵活:
// 之前:需要单独的语句
n := new(int)
*n = 42
// 之后:表达式形式
n := new(int)
*n = new(42) // new 可以接受表达式
实际影响:处理序列化中的可选字段(如带指针字段的 encoding/json)变得更简洁。
值得了解的运行时改进
Go 1.26 带来了一个新的垃圾收集器,通过区分"必须收集"和"可能收集"的工作来减少暂停时间。结果:延迟敏感工作负载的 stop-the-world 暂停更少。
更快的 cgo 调用在同一版本中落地。如果你从 Go 调用 C,开销显著下降。这不是对每个人的变化,但对需要的人来说很重要。
堆基地址随机化作为安全加固措施到来。Go 堆的 ASLR,使利用更加困难。
还有一个实验性的 goroutine 泄漏分析器。go tool trace 现在可以识别 goroutine 泄漏的位置。如果你曾经有一个服务慢慢积累 goroutine 直到崩溃,这就是你一直在等待的调试工具。
什么没有改变
Go 的哲学在所有四个版本中保持一致:保持兼容性、改进工具链、避免语言复杂性。
没有重要的语法添加。没有重大的范式转变。语言仍然很小、仍然可读、仍然为团队设计。
这既是特性也是限制取决于你将其与什么比较。Go 没有 Rust trait 的表达力,也没有 Haskell 类型类的多态性。但它也没有理解这些特性的认知开销。
升级的数学
Go 1.26 需要 Go 1.22 或更高版本才能构建。如果你使用的是旧版本,你不会获得安全补丁。
仅仅 for 循环修复就值得升级。泛型改进在你编写数据结构时很重要。GC 改进在你关心尾延迟时很重要。
成本:在新版本上测试你的代码。好处:更容易推理、更少的意外、更好的性能。
对大多数团队来说,数学很简单。保持最新。
下一篇:实用的 Go 项目结构 — 如何组织一个两年后仍然有意义的 Go 应用