为什么这篇文章同时涵盖四个次要版本

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 应用