Go

The Go Blog

再见 core types,你好,我们所熟悉和喜爱的 Go!

Robert Griesemer 2025年3月26日

Go 1.18 版本引入了泛型,并随之带来了一些新特性,包括类型参数、类型约束以及类型集合等新概念。它还引入了一个名为 core type(核心类型)的概念。虽然前述特性提供了具体的新功能,但 core type 是一个抽象的结构,其引入是为了方便和简化处理泛型操作数(类型为类型参数的操作数)。在 Go 编译器中,过去依赖于操作数的底层类型的代码,现在必须调用一个计算操作数 core type 的函数。在语言规范中,我们只需要在很多地方将“底层类型”替换为“core type”。 看起来不错,不是吗?

事实证明,问题还真不少! 为了理解我们是如何走到这一步的,简要回顾一下类型参数和类型约束的工作方式是很有帮助的。

类型参数和类型约束

类型参数是未来类型实参的占位符; 它的作用类似于一个_类型变量_,其值在编译时已知,类似于命名常量代表一个在编译时已知其值的数字、字符串或布尔值。 像普通变量一样,类型参数也有一个类型。 该类型由它们的_类型约束_来描述,类型约束决定了对类型为相应类型参数的操作数允许执行哪些操作。

任何实例化类型参数的具体类型都必须满足类型参数的约束。 这确保了类型为类型参数的操作数具有相应类型约束的所有属性,无论使用什么具体类型来实例化类型参数。

在 Go 中,类型约束通过方法和类型要求的组合来描述,它们共同定义了一个_类型集合_:这是满足所有要求的所有类型的集合。 Go 为此目的使用了广义形式的接口。 接口枚举了一组方法和类型,并且由此类接口描述的类型集合由所有实现这些方法并包含在枚举类型中的类型组成。

例如,接口描述的类型集合

type Constraint interface {
  ~[]byte | ~string
  Hash() uint64
}

由其表示形式为 []bytestring 并且其方法集合包括 Hash 方法的所有类型组成。

有了这些,我们现在可以写下管理泛型操作数操作的规则。 例如,索引表达式的规则指出,(除此之外)对于类型参数类型为 P 的操作数 a

索引表达式 a[x] 对于 P 的类型集合中所有类型的值必须有效。 P 的类型集合中所有类型的元素类型必须相同。(在这种上下文中,字符串类型的元素类型是 byte。)

这些规则使得索引下面的泛型变量 s 成为可能 (playground):

func at[bytestring Constraint](s bytestring, i int) byte {
  return s[i]
}

允许索引操作 s[i],因为 s 的类型是 bytestring,并且 bytestring 的类型约束(类型集合)包含 []bytestring 类型,对于这些类型,使用 i 进行索引是有效的。

Core types

这种基于类型集合的方法非常灵活,并且符合原始泛型提案的意图:如果涉及泛型类型操作数的操作对于相应类型约束允许的任何类型都有效,那么该操作应该是有效的。 为了简化实现的考虑,知道我们以后可以放宽规则,因此_没有_普遍选择这种方法。 相反,例如,对于发送语句,规范指出

通道表达式的 core type 必须是一个通道,通道方向必须允许发送操作,并且要发送的值的类型必须可以分配给通道的元素类型。

这些规则基于 core type 的概念,core type 的定义大致如下:

例如,interface{ ~[]int } 有一个 core type ([]int),但上面的 Constraint 接口没有 core type。 更复杂的是,当涉及到通道操作和某些内置调用(appendcopy)时,上述 core type 的定义过于严格。 实际规则有调整,允许不同的通道方向和包含 []bytestring 类型的类型集合。

这种方法存在各种问题:

Go 1.25

对于即将发布的 Go 1.25 版本(2025 年 8 月),我们决定从语言规范中删除 core type 的概念,转而采用在需要时明确的(并且等效的!)文字描述。 这有多种好处:

相应的提案 issue #70128 最近已获批准,并且相关的更改已实施。 具体来说,这意味着语言规范中的大量文字恢复到了其原始的、预泛型的形式,并在需要时添加了新的段落以解释适用于泛型操作数的规则。 重要的是,没有改变任何行为。 整个关于 core type 的部分都被删除了。 编译器的错误消息已更新为不再提及 “core type”,并且在许多情况下,错误消息现在更加具体,可以通过指出类型集合中究竟是哪种类型导致了问题。

这是一个更改示例。 对于内置函数 close,从 Go 1.18 开始,规范开始如下:

对于 core type 是通道的参数 ch,内置函数 close 记录不再在该通道上发送任何值。

只想知道 close 如何工作的读者必须首先了解 core type。 从 Go 1.25 开始,本节将再次以与 Go 1.18 之前相同的方式开始:

对于通道 ch,内置函数 close(ch) 记录不再在该通道上发送任何值。

这更短且更易于理解。 只有当读者处理泛型操作数时,他们才必须考虑新添加的段落:

如果 close 的参数的类型是类型参数,则其类型集合中的所有类型都必须是具有相同元素类型的通道。 如果这些通道中的任何一个都是只接收通道,则会发生错误。

我们对每个提到 core type 的地方都做了类似的更改。 总之,虽然此规范更新不会影响任何当前的 Go 程序,但它为未来的语言改进打开了大门,同时使当今的语言更容易学习,并使其规范更简单。

上一篇文章:Traversal-resistant file APIs 博客索引