泛型中类型参数的赋值
1 简介
关于类型参数的赋值,首先,我们应该知道普通类型/值的赋值规则。
在以下说明中,目标值的类型称为目标类型,源值的类型称为源类型。
根据当前规范(Go 1.22),对于涉及类型参数的赋值,如果目标类型是类型参数,而源值是 非类型化值,则赋值仅在以下情况下有效 非类型化值可分配给 目标类型参数。
如果目标类型是类型参数,而源类型是普通类型, 然后,仅当源普通类型未命名且其值可分配给目标类型参数的类型集中的每个类型时,赋值才有效。
如果源类型是类型参数,而目标类型是普通类型, 则仅当目标普通类型未命名时,赋值才有效 以及源类型参数的类型集中每种类型的值 可分配给目标普通类型。
如果目标类型和源类型都是类型参数, 则分配无效。
从规则中,我们可以得到一个命名类型的类型值不能分配给另一个命名类型。
在以下代码片段中,有四个无效的赋值。
func dat[T ~int | ~float64, S *int | []bool]() {
var _ T = 123 // okay
var _ S = nil // okay
}
func zum[T []byte](x []byte) {
var t T = x // okay
type Bytes []byte
var y Bytes = x // okay (both are ordinary types)
x = t // okay
x = y // okay
// Both are named types.
t = y // error
y = t // error
// To make the above two assignments valid,
// the sources in then must be converted.
t = T(y) // okay
y = Bytes(t) // okay
}
func pet[A, B []byte](x A, y B){
// Both are type parameters.
x = y // error: cannot use y as type A in assignment
y = x // error: cannot use x as type B in assignment
}
目前尚不清楚未来的 Go 版本是否会放宽分配规则。 看起来可能性很小。
2 对预先声明的函数的调用
以下是调用某些预声明函数的一些规则和详细信息 当涉及类型参数时。
如果len对参数类型集中的所有类型都有效,则cap对预先声明的函数的调用是有效的
在以下代码片段中,capacity函数无法编译, 其他两个函数都可以编译正常。
type Container[T any] interface {
~chan T | ~[]T | ~[8]T | ~*[8]T | ~map[int]T | ~string
}
func size[T Container[int]](x T) int {
return len(x) // okay
}
func capacity[T Container[int]](x T) int {
return cap(x) // error: invalid argument x for cap
}
func capacity2[T ~chan int | ~[]int](x T) int {
return cap(x) // okay
}
请注意,对 cap OR len 的调用始终返回非常量值 如果调用的参数类型是类型参数, 即使参数的类型集也只包含数组和指向数组的指针。 例如,在下面的代码中, 第一个函数和前两个函数中的调用 len全部编译失败。
func f[T [2]int](x T) {
const _ = cap(x) // error: cap(x) is not constant
const _ = len(x) // error: len(x) is not constant
var _ = cap(x) // okay
var _ = len(x) // okay
}
func g[P *[2]int](x P) {
const _ = cap(x) // error: cap(x) is not constant
const _ = len(x) // error: len(x) is not constant
var _ = cap(x) // okay
var _ = len(x) // okay
}
func h(x [2]int) {
const _ = cap(x) // okay
const _ = len(x) // okay
const _ = cap(&x) // okay
const _ = len(&x) // okay
}
规则可能会更改。 但老实说,这种可能性非常小。 个人而言,我认为目前的行为更合乎逻辑。
由于此规则,以下两个函数返回不同的结果。
package main
const S = "Go"
func ord(x [8]int) byte {
return 1 << len(x) >> len(x)
}
func gen[T [8]int](x T) byte {
return 1 << len(x) >> len(x)
}
func main() {
var x [8]int
println(ord(x), gen(x)) // 1 0
}
再次,请阅读 Go 文章中的字符串 以及为什么这两个函数返回不同结果的问题。
请不要这样,以下函数无法编译, 因为 的类型是 ,它是一个指针 类型参数,而不是指向数组的指针。&x*T
func e[T [2]int]() {
var x T
var _ = len(&x) // invalid argument: &x for len
var _ = cap(&x) // invalid argument: &x for cap
}
换言之,类型集的类型参数仅包含一种类型 不等同于该 only 类型。 类型参数具有波粒对偶性。
在某些情况下,它充当其类型集中的类型。 对于其他一些情况,它充当一种不同的类型。 更具体地说,类型参数充当非单一类型 (不与任何其他类型共享基础类型) 当它用作复合类型的组件时。 在上面的例子中。
并且是两种不同的(普通)类型。
对预先声明的函数的调用对其参数没有额外的要求new,以下函数编译正常。
func MyNew[T any]() *T {
return new(T)
}
它相当于
func MyNew[T any]() *T {
var t T
return &t
}
对预声明函数的调用要求其第一个参数(容器类型)具有核心类型。
目前(Go 1.22),在下面的代码片段中,函数make和vocted两者 编译失败,其他两个编译正常。
原因是调用预声明函数的第一个参数 需要具有核心类型。 make和 vocted函数都不满足此要求, 而其他两个函数都满足此要求。
func voc[T chan bool | chan int]() {
_ = make(T) // error: invalid argument: no core type
}
func ted[T chan<- int | <-chan int]() {
_ = make(T) // error: invalid argument: no core type
}
type Stream chan int
type Queue Stream
func fat[T Stream | chan int | Queue | chan<- int]() {
_ = make(T) // okay
}
func nub[T Stream | chan int | Queue | <-chan int]() {
_ = make(T) // okay
}
个人理解,这个要求是为了进行后续操作 在制造的容器上(在上面的例子中它们是通道)总是合法的。 例如,要确保从 made 接收到的值 channel 具有指定的类型(类型参数或普通类型)。
就我个人而言,我认为要求过于严格。 毕竟,在某些情况下,假定的后续操作不会发生。
若要在泛型函数中使用没有核心类型的类型参数的值, 我们可以将这些值作为值参数传递到函数中,如以下代码所示。
func doSomething(any) {}
func voc2[T chan bool | chan int](x T) {
doSomething(x)
}
func ted2[T chan<- int | <-chan int](x T) {
doSomething(x)
}
由于相同的要求,以下两个函数均不编译。
func zig[T ~[]int | map[int]int](c T) {
_ = make(T) // error: invalid argument: no core type
}
func rat[T ~[]int | ~[]bool](c T) {
_ = make(T) // error: invalid argument: no core type
}
对预先声明的new函数的调用没有此要求。
对预声明函数的调用要求其第一个参数的类型集中的所有类型都具有相同的键类型delete
注意,这里相同的键类型可以是普通类型,也可以是类型参数类型。
以下函数都可以编译正常。
func zuk[M ~map[int]string | ~map[int]bool](x M, k int) {
delete(x, k)
}
func pod[M map[K]int | map[K]bool, K ~int | ~string](x M, k K) {
delete(x, k)
}
对预声明函数的调用要求其参数的类型集中的所有类型都是通道类型close
以下函数编译正常。
func dig[T ~chan int | ~chan bool | ~chan<- string](x T) {
close(x)
}
请注意,当前的 Go 规范要求 close对预声明函数的调用必须具有核心类型。 但上面的例子不满足这个要求。 这与官方标准 Go 编译器的实现不一致。
对预声明的 complex和realimag函数的调用现在不接受参数类型的参数
使用类型参数的参数调用这三个函数可能会违反本章第一节中提到的原则规则。
这是当前自定义泛型设计无法解决的问题。 这有一个问题。
3 具有空类型集的约束
某些接口类型的类型集可能为空。 空类型集接口类型实现任何接口类型, 包括它自己。
空类型集接口类型在实践中是完全没用的, 但从理论的角度来看,它们可能会影响实现的完美性。
在实现中确实存在一些不完美之处 当前官方标准 Go 编译器 (v1.22.n)。
例如, 是否应该编译以下函数? 它与最新的官方标准 Go 编译器 (v1.22.n) 一起使用。 但是,上述部分之一提到调用 要求其参数必须具有核心类型。 在以下代码中声明的约束的类型集 是空的,所以它没有核心类型,那么里面的调用 该函数不应编译。makeCmakefoo
// This is an empty-type-set interface type.
type C interface {
map[int]int
M()
}
func foo[T C]() {
var _ = make(T)
}
下面是另一个例子, 其中函数中的所有函数调用都应该编译正常。 但是,其中两个无法编译 最新的官方标准 Go 编译器 (v1.22.n)。
func f1[T any](x T) {}
func f2[T comparable](x T) {}
func f3[T []int](x T) {}
func f4[T int](x T) {}
// This is an empty-type-set interface type.
type C interface {
[]int
m()
}
func g[V C](v V) {
f1(v) // okay
f2(v) // error: V does not implement comparable
f3(v) // okay
f4(v) // error: V does not implement int
}
当前的 Go 规范特别指出:
实现限制:如果操作数的类型是类型集为空的类型参数,则编译器无需报告错误。无法实例化具有此类类型参数的函数;任何尝试都会导致实例化站点出现错误。
所以上面显示的缺陷并不是官方标准 Go 编译器的错误。
对于即将发布的 Go 1.25 版本(2025 年 8 月),我们决定从语言规范中删除核心类型的概念,以支持在需要时使用显式(和等效的)散文。这有多种好处:
Go 规范提供的概念较少,因此更容易学习该语言。
4 小结
无需参考泛型概念即可理解非泛型代码的行为。
个性化方法(针对特定作的特定规则)为更灵活的规则打开了大门。我们已经提到了问题 #48522,但也有关于更强大的切片作和改进类型推断的想法。
相应的提案问题 #70128 最近已获得批准,相关更改已实施。具体来说,这意味着语言规范中的许多散文都恢复到其原始的、前泛型形式,并在需要的地方添加了新段落来解释与泛型作数相关的规则。重要的是,没有改变任何行为。删除了关于核心类型的整个部分。
编译器的错误消息已更新,不再提及“核心类型”,并且在许多情况下,错误消息现在更加具体,可以准确指出类型集中的哪种类型导致了问题。
以下是所做的更改的示例。对于内置函数 ,从 Go 1.18 开始,close规范开始如下:
对于核心类型为通道的参数,内置函数close记录不会在通道ch上发送更多值。
一个只想知道工作原理的读者必须首先了解核心类型。从 Go 1.25 开始,本节将再次以与 Go 1.18 之前相同的方式开始:
对于通道。ch,内置函数记录不会在close(ch)通道上发送更多值
这更短,更容易理解。只有当读者处理一个通用作数时,他们才必须考虑新添加的段落:
如果参数的类型是类型参数,则其类型集中的所有类型都必须是具有相同元素类型的通道。如果其中任何一个通道是仅接收通道,则为错误。
- 点赞
- 收藏
- 关注作者
评论(0)