Go 1.27 Interactive Tour

Go 1.27 Interactive Tour

Go 1.27 is coming soon, so it’s a good time to get a head start on what’s new. The official release notes are pretty dry, so here’s a hands-on version with runnable examples showing what changed and how the new behavior works. Go 1.27 即将发布,现在是抢先了解新特性的好时机。官方发布说明通常比较枯燥,因此这里提供一个带有可运行示例的实践版本,展示了具体的变化以及新特性的工作方式。

A quick credit first: the interactive Go tours were started by Anton Zhiyanov, who wrote one for every release from Go 1.22 through Go 1.26. He’s decided to stop, so we’re picking up where he left off. 首先要致谢:交互式 Go 教程系列由 Anton Zhiyanov 发起,他为从 Go 1.22 到 Go 1.26 的每一个版本都编写了教程。由于他决定停止更新,我们将接手并继续这项工作。

Before we start digging into the new features, let’s set the context. This article is based on the official release notes and the Go source code, licensed under the BSD-3-Clause. This is not an exhaustive list; see the official release notes for that. 在深入探讨新特性之前,先说明一下背景。本文基于官方发布说明和 Go 源代码(采用 BSD-3-Clause 许可)。这不是一份详尽的列表;完整内容请参阅官方发布说明。

Links point to the documentation (𝗗), proposals (𝗣), most relevant commits (𝗖𝗟), and authors (𝗔) for each feature; check them out for motivation, usage, and implementation details. 链接指向了每个特性的文档 (𝗗)、提案 (𝗣)、最相关的提交 (𝗖𝗟) 以及作者 (𝗔);你可以查看这些链接以了解其动机、用法和实现细节。


Generic methods

泛型方法

This is the headline of the release. A method declaration may now declare its own type parameters, independent of the receiver’s. Before Go 1.27, only top-level functions could be generic, so a generic operation on a type had to live as a package-level function instead of a method. 这是本次发布的核心亮点。方法声明现在可以声明自己的类型参数,且独立于接收者。在 Go 1.27 之前,只有顶层函数可以是泛型的,因此对类型的泛型操作必须作为包级函数存在,而不能作为方法。

Say we have a generic container and want a Map operation that can change the element type: 假设我们有一个泛型容器,并希望实现一个可以改变元素类型的 Map 操作:

type Box[T any] struct{ v T }

// The method declares its own type parameter U (new in Go 1.27).
func (b Box[T]) Map[U any](f func(T) U) Box[U] {
    return Box[U]{v: f(b.v)}
}

Now Map is a method of Box and can transform an int box into a string box: 现在 Map 成为了 Box 的一个方法,可以将一个整数类型的 Box 转换为字符串类型的 Box:

func main() {
    b := Box[int]{v: 21}
    doubled := b.Map(func(n int) int { return n * 2 })
    label := doubled.Map(func(n int) string {
        return fmt.Sprintf("value=%d", n)
    })
    fmt.Println(label.v)
}
// Output: value=42

There is one important restriction: interfaces still can’t declare type-parameterized methods, and a generic method can’t be used to satisfy an interface. 有一个重要的限制:接口仍然不能声明带有类型参数的方法,且泛型方法不能用于满足接口。

𝗗 Generic methods | 𝗣 77273 | 𝗖𝗟 524b860, e84da04, e212a16 | 𝗔 Robert Griesemer, Mark Freeman


Struct literal field selectors

结构体字面量字段选择器

A key in a struct literal may now be any valid field selector for the struct type, not just a top-level field name. In practice this means you can set a promoted field (one that comes from an embedded struct) directly, without spelling out the embedded type. 结构体字面量中的键现在可以是该结构体类型的任何有效字段选择器,而不仅仅是顶层字段名。实际上,这意味着你可以直接设置提升字段(来自嵌入结构体的字段),而无需写出嵌入类型。

type Base struct { ID int }
type User struct { Base; Name string }

// Before Go 1.27 you had to write User{Base: Base{ID: 7}, Name: "Mittens"}.
// Now the promoted ID works as a key on its own:
u := User{ID: 7, Name: "Mittens"}
fmt.Println(u.ID, u.Name)
// Output: 7 Mittens

𝗗 Composite literals | 𝗣 9859 | 𝗖𝗟 1a8f9d8, 9f7e98d, 30bfe53, e2c1885 | 𝗔 Robert Griesemer, Cherry Mui


Generalized function type inference

通用函数类型推断

Function type inference has been generalized to apply in all contexts where a generic function is used where a matching function type is expected: not just plain assignment to a variable (which already worked), but also conversions and composite literals. 函数类型推断已得到泛化,适用于所有期望匹配函数类型的泛型函数使用场景:不仅限于简单的变量赋值(这在之前已经支持),还包括类型转换和复合字面量。

func first[T any](s []T) T { return s[0] }
func last[T any](s []T) T { return s[len(s)-1] }

// The slice's element type drives inference: T=int for each entry.
ops := []func([]int) int{first, last}
for _, op := range ops {
    fmt.Println(op([]int{10, 20, 30}))
}
// Output: 10 30

𝗗 Assignability | 𝗣 77245 | 𝗖𝗟 ef06728, f757de8 | 𝗔 Robert Griesemer, Mark Freeman


Faster memory allocation

更快的内存分配

The compiler now generates calls to size-specialized memory allocation routines, cutting the cost of some small (under 80 bytes) allocations by up to 30%. Improvements vary with the workload, but the overall gain is expected to be around 1% in real allocation-heavy programs. 编译器现在会生成针对特定大小的内存分配例程调用,将某些小型(80 字节以下)分配的开销降低了多达 30%。改进效果因工作负载而异,但在实际内存分配密集型程序中,整体性能提升预计在 1% 左右。

There’s nothing to change in your code; it just gets a little faster. If you need to turn it off, build with GOEXPERIMENT=nosizespecializedmalloc. 你的代码无需任何改动,它会自动变得更快。如果需要关闭此功能,可以使用 GOEXPERIMENT=nosizespecializedmalloc 进行构建。

𝗗 Runtime release notes | 𝗣 79286 | 𝗖𝗟 2a93576 | 𝗔 Michael Matloob


Goroutine labels in tracebacks

回溯中的 Goroutine 标签

For modules whose go.mod sets Go 1.27 or later, tracebacks now include runtime/pprof goroutine labels in the header line of each goroutine. If you already attach labels for profiling with pprof.Do, that context now shows up in crash dumps, SIGQUIT traces, and runtime.Stack output. 对于 go.mod 设置为 Go 1.27 或更高版本的模块,回溯信息现在会在每个 goroutine 的标题行中包含 runtime/pprof 的 goroutine 标签。如果你已经使用 pprof.Do 添加了分析标签,这些上下文现在也会出现在崩溃转储、SIGQUIT 跟踪和 runtime.Stack 的输出中。

ctx := context.Background()
pprof.Do(ctx, pprof.Labels("request", "42"), func(ctx context.Context) {
    buf := make([]byte, 1<<12)
    n := runtime.Stack(buf, false)
    fmt.Printf("%s", buf[:n])
})
// Output:
// goroutine 1 [running] {request: 42}:
// main.main.func1(...) .../main.go:14 +0x38

What’s new is the {request: 42} appended right after the goroutine’s [running] state: its pprof labels. 新变化是紧跟在 goroutine [running] 状态之后附加的 {request: 42},这就是它的 pprof 标签。