Go 1.27 中的泛型方法
Generic Methods in Go 1.27

原始链接: https://dominik.info/blog/go-generic-methods

Go 1.27 终于引入了对泛型方法的支持,允许方法定义独立于接收者结构体的类型参数。 在此之前,如果一个方法需要结构体本身未使用的泛型类型(例如将 `T` 转换为 `U` 的 `Map` 函数),开发者只能被迫在结构体定义中添加多余的类型参数,或者依赖不够直观的包级函数。通过此次更新,像 `U` 这样的类型参数现在可以严格限定在方法作用域内,从而使 API 设计更加简洁且符合 Go 语言的惯用法。 这一功能的实现推迟,源于 Go 语言编译时单态化(为特定类型生成代码)与运行时接口分发之间的技术冲突。在接口中支持泛型方法本需要复杂的即时编译(JIT)技术,或会导致二进制文件体积大幅膨胀。为了平衡灵活性与性能,Go 团队决定仅允许在具体类型上使用泛型方法,并明确将其排除在接口之外。这一折中方案在有效解决泛型方法最常见用例的同时,也保持了 Go 语言的性能标准。

Hacker News | 最新 | 过往 | 评论 | 提问 | 展示 | 招聘 | 提交 | 登录 Go 1.27 中的泛型方法 (dominik.info) 9 分,EspressoGPT 发布于 53 分钟前 | 隐藏 | 过往 | 收藏 | 1 条评论 | 帮助 tancop 14 分钟前 [-] > Go 的接口系统是在运行时起作用的。传递给接口参数的值的具体类型是在程序运行时解析的。这种动态分发与在编译时解析的泛型存在冲突。如果编译器为每个方法同时生成虚函数表(vtable)和单态化变体呢?大多数调用可以使用静态版本,而在需要的地方(如接口泛型)则调用虚拟方法。 回复 准则 | 常见问题 | 列表 | API | 安全 | 法律 | 加入 YC | 联系 搜索:
相关文章

原文

When Go 1.18 introduced Generics in 2022, it brought generic type parameters to functions and structs, but left out methods. With the release of Go 1.27, this long-standing limitation has been removed. Methods can now define their own type parameters without adding them to the receiving struct.

Why this change was introduced

If you want to create a graph node that holds a generic value, you might implement it like this:

type Node[T any] struct {
    value T
}

Imagine adding a method Map that transforms a node of type T into a node of another type U. Prior to Go 1.27, you were forced to add U directly to the Node struct itself:

type Node[T any, U any] struct {
    value T
}

func (n *Node[T, U]) Map() Node[T, U] {
    // ...
}

Adding U to the struct itself is bad design because U is a type parameter specific to Map. Even though other methods wouldn’t use U, they still had to keep it in their receiver declarations.

The only workaround was to implement Map as a package-level function instead of a method, since functions could define their own type parameters. But methods couldn’t, leading to awkward and non-idiomatic code API designs.

Starting in Go 1.27, U can be defined exclusively on the Map method:

func (n *Node[T]) Map[U any]() Node[U] {
    // ...
}

Why did it take so long?

The answer lies in how generics are implemented in Go. The compiler handles generics primarily using monomorphization, meaning it will create a copy of generic structs or functions for every specific type they’re used with. Because at some point, the abstract concept of generics needs to be translated to straight-forward machine code.

However, Go’s interface system works at runtime. The specific type of a value passed into an interface parameter is resolved while the program runs. This dynamic dispatch clashes with generics being resolved at compile time. Consider what would happen if we wanted to declare our Map method in an interface:

type Mapper interface {
    Map[T any, U any](element T) U 
}

To make this work, the runtime would either need a Just-In-Time compiler to generate machine code for specific types on the fly, or it would need to create said copies for every possible type upfront, resulting in a massively bloated binary.

Ultimately, the Go team decided to separate generic methods from interfaces. Go 1.27 allows generic methods on concrete types, but not on interfaces. This distinction is why the above example won’t compile and why the discussions around it took a while.

Further Reading

联系我们 contact @ memedata.com