Go 1.27 中的泛型方法
摘要
Go 1.27 引入了泛型方法,允许方法在不修改接收结构体的情况下定义自己的类型参数,从而解决了 Go 1.18 中的局限性。但由于运行时约束,接口仍被排除在外。
暂无内容
查看缓存全文
缓存时间: 2026/08/20 16:22
# Go 1.27 中的泛型方法
来源:https://dominik.info/blog/go-generic-methods
当 Go 1.18 在 2022 年引入泛型(https://dominik.info/blog/go-generics-introduction)时,它将泛型类型参数带到了函数和结构体中,但方法却未被涵盖。随着 Go 1.27 的发布,这一长期存在的限制已被移除。现在,方法可以定义自己的类型参数,而无需将这些参数添加到接收结构体上。
## 为何引入此变更
如果你想创建一个持有泛型值的图节点,你可能会这样实现:
```
type Node[T any] struct {
value T
}
```
想象添加一个名为 `Map` 的方法,该方法将类型为 `T` 的节点转换为另一种类型 `U` 的节点。在 Go 1.27 之前,你被迫将 `U` 直接添加到 `Node` 结构体本身:
```
type Node[T any, U any] struct {
value T
}
func (n *Node[T, U]) Map() Node[T, U] {
// ...
}
```
将 `U` 添加到结构体本身是一种糟糕的设计,因为 `U` 是专属于 `Map` 的类型参数。即使其他方法不会使用 `U`,它们也必须在其接收者声明中保留它。
唯一的解决方法是将 `Map` 实现为包级别的*函数*,而不是方法,因为函数可以定义自己的类型参数。但方法不能,这导致了笨拙且不地道的 API 设计代码。
从 Go 1.27 开始,`U` 可以仅定义在 `Map` 方法上:
```
func (n *Node[T]) Map[U any]() Node[U] {
// ...
}
```
## 为何花了这么长时间?
答案在于 Go 中泛型的实现方式(https://dominik.info/blog/go-generics-performance)。编译器主要使用*单态化*来处理泛型,这意味着它会为泛型结构体或函数使用的每种具体类型创建一个副本。因为在某个时刻,泛型的抽象概念需要被转换为直接的机器码。
然而,Go 的接口系统在*运行时*工作。传递给接口参数的值的具体类型在程序运行时被解析。这种动态分派与在编译时解析泛型的机制相冲突。考虑一下如果我们想在接口中声明我们的 `Map` 方法会发生什么:
```
type Mapper interface {
Map[T any, U any](element T) U
}
```
为了使其工作,运行时要么需要一个即时编译器来动态生成特定类型的机器代码,要么需要为所有可能的类型预先创建这些副本,从而导致二进制文件的体积急剧膨胀。
最终,Go 团队决定将泛型方法与接口分离。Go 1.27 允许在具体类型上使用泛型方法,但*不允许*在接口上使用。这种区分是上述示例无法编译的原因,也是相关讨论持续了一段时间的原因。
## 延伸阅读
- Go 1.27 发行说明(go.dev)(https://go.dev/doc/go1.27)
- 提案:为 Go 添加泛型方法(GitHub)(https://github.com/golang/go/issues/77273)
- Go 中泛型的性能(dominik.info)(https://dominik.info/blog/go-generics-performance)
相似文章
Go 1.27 发行说明
Go 1.27 发布引入了泛型方法以及其他语言和工具链更新,同时保持与之前版本的兼容性。
Golang提案:container/ 泛型集合类型
本文讨论了一个提案,旨在向Go的container包添加泛型集合类型,通过泛型扩展标准库的功能。
Go 泛型中的 GC shape stenciling
深入解释 Go 编译器如何使用 GC shape stenciling 实现泛型,并与 Rust 的 full monomorphization 和 Java 的 type erasure 进行比较。
Go 1.27 发布 - Go 编程语言
Go 1.27 已发布,包含重大增强,如泛型方法、性能改进以及新标准库特性,例如 encoding/json/v2 和后量子密码学支持。
Go 1.27 交互式导览
Go 1.27 新功能的实践性交互式导览,重点介绍泛型方法、结构体字面量字段选择器等,并提供基于官方发布说明的可运行示例。