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. His earlier tours are all still worth a read: 首先致谢:交互式 Go 教程系列由 Anton Zhiyanov 发起,他为从 Go 1.22 到 Go 1.26 的每一个版本都编写了教程。由于他决定停止更新,我们将接手继续这项工作。他之前的教程依然非常值得一读:

Thanks, Anton. 感谢你,Anton。

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. The authors (𝗔) are the people who contributed to the feature (writing the implementation, the tests, or, for features that graduated from an earlier experiment, the original design), not necessarily a single main author. 链接指向每个特性的文档 (𝗗)、提案 (𝗣)、最相关的提交 (𝗖𝗟) 和作者 (𝗔);请查看这些链接以了解其动机、用法和实现细节。作者 (𝗔) 是指为该特性做出贡献的人(编写实现、测试,或者对于从早期实验中毕业的特性,指原始设计者),而不一定仅指某一位主要作者。

Error handling is often skipped to keep the examples short. Don’t do this in production ツ 为了保持示例简洁,通常会省略错误处理。在生产环境中请勿这样做 ツ


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 的一个方法,可以将一个 int 类型的 Box 转换为 string 类型的 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)
}
// 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. Put a generic method in an interface and the compiler stops you: 有一个重要的限制:接口仍然不能声明带有类型参数的方法,且泛型方法不能用于满足接口。如果在接口中放入泛型方法,编译器会报错:

type Mapper interface {
    Map[U any](f func(int) U) any // interfaces can't declare generic methods
}
// interface method must have no type parameters

𝗗 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: 在 Go 1.27 之前,你必须写成 User{Base: Base{ID: 7}, Name: "Mittens"}。现在,提升后的 ID 可以直接作为键使用:

u := User{ID: 7, Name: "Mittens"}
fmt.Println(u.ID, u.Name)
// 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. In those cases you previously had to spell out the type arguments by hand. 函数类型推断已得到推广,适用于所有期望匹配函数类型的泛型函数使用场景:不仅限于简单的变量赋值(这在之前已经支持),还包括类型转换和复合字面量。在这些情况下,以前你必须手动写出类型参数。

Take two generic helpers and drop them into a slice whose element type is func([]int) int: 以两个泛型辅助函数为例,将它们放入一个元素类型为 func([]int) int 的切片中:

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.
// Before Go 1.27 this failed with "cannot use generic function without instantiation";
// you had to write first[int], last[int].
ops := []func([]int) int{first, last}

for _, op := range ops {
    fmt.Println(op([]int{10, 20, 30}))
}
// 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. The tradeoff is about 60 KB of extra binary size, independent of the workload. 编译器现在会生成针对特定大小的内存分配例程调用,将某些小型(小于 80 字节)分配的开销降低了多达 30%。改进效果因工作负载而异,但在实际内存分配密集型程序中,整体性能提升预计在 1% 左右。代价是二进制文件大小会增加约 60 KB,这与工作负载无关。

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. That opt-out is expected to be removed in Go 1.28. 你的代码无需任何更改,它会自动变得更快。如果需要关闭此功能,请使用 GOEXPERIMENT=nosizespecializedmalloc 进行构建。该选项预计将在 Go 1.28 中移除。

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


Goroutine labels in tracebacks (Traceback 中的 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 too (handy for telling apart otherwise identical goroutines). 对于 go.mod 设置为 Go 1.27 或更高版本的模块,Traceback 现在会在每个 Goroutine 的标题行中包含 runtime/pprof 的 Goroutine 标签。如果你已经使用 pprof.Do 附加了分析标签,那么该上下文现在也会出现在崩溃转储、SIGQUIT 跟踪和 runtime.Stack 输出中(这对于区分原本相同的 Goroutine 非常有用)。

Here we attach a label, then dump the current goroutine’s stack to see it in action: 这里我们附加一个标签,然后转储当前 Goroutine 的堆栈以查看效果:

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])
})
goroutine 1 [running] {request: 42}:
main.main.func1(...)
    .../main.go:14 +0x38
runtime/pprof.Do(...)
    .../runtime/pprof/runtime.go:57 +0x8c
main.main()
    .../main.go:12 +0x6c

The pointer arguments, offsets, and file paths differ from run to run; what’s new is the {request: 42} appended right after the goroutine’s [running] state: its pprof labels. That same {...} annotation appears on the header of every labeled goroutine in a panic or SIG. 指针参数、偏移量和文件路径每次运行都会不同;新变化是紧跟在 Goroutine [running] 状态之后附加的 {request: 42},即它的 pprof 标签。同样的 {...} 注解也会出现在 panic 或 SIG 信号中每个带有标签的 Goroutine 标题上。