在下面的代码中,我定义了一个通用的链表,Go1.18很乐意使用这个链表的一个示例作为Map的键,但是最后一行没有注解的话就不能编译;我得到错误:
Cons[int]未实现可比
是否有一个较弱的类型约束可以用来挑选出那些可以用作键的类型,或者这是有意的,或者是一个编译器错误?
package main
import "fmt"
type List[X any] interface {
isList()
}
type Cons[X any] struct {
Data X
Next List[X]
}
func (Cons[X]) isList() {}
type Nil[X any] struct{}
func (Nil[X]) isList() {}
func id[X comparable](x X) X { return x }
func main() {
x := Cons[int]{5, Nil[int]{}}
m := map[List[int]]string{}
m[x] = "Hi" // succeeds
fmt.Println(m[x]) // prints "Hi"
// fmt.Println(id(x)) // fails
}
1条答案
按热度按时间iyr7buue1#
Go 1.20(2023年2月)
comparable
是Map键的正确捕获所有约束。所有符合Go语言规范的可比较类型,即使在运行时比较异常,也可以满足
comparable
约束,代码将按照预期在1.20中编译。这最终修正了之前Go语言版本中关于spec-comparable类型和
comparable
类型的不一致,详见下文。转到1.18和1.19
预声明的
comparable
约束是Map键的正确约束,但是它只能由严格可比的类型示例化,即支持==
和!=
(用作Map键的条件)但不会在运行时死机的类型。这不包括接口1。这是在这里提到:https://go.dev/ref/spec#Type_constraints
预先声明的接口类型comparable表示所有可比较的非接口类型的集合。具体地说,类型T实现comparable,如果:
T
不是接口类型,并且T
支持操作==
和!=
2T
是接口类型,并且T
的类型集中的每个类型实现comparable
即使可以比较不是类型参数的接口(可能导致运行时死机),它们也不实现comparable。
这是一个很重要的问题,因为基本接口类型通常都支持相等操作符--比较的是它们的动态类型/值。
因此,您的接口
List[X]
可以直接用作Map键,就像map[List[int]]string{}
一样,但是它没有实现comparable
,因为它有一个无限的类型集(它没有项,所以任何类型都可以实现它),并且Cons
也没有实现它,因为它有一个类型为List[X]
的字段,对此没有“较弱”的约束。考虑到嵌入
comparable
的约束对于map键也是有效的,所以如果你真的需要函数体中的方法isList()
,你可以像这样定义一个约束,并让你的lists-that-are-map-key结构实现它,而不是声明一个接口字段:1:从规范中引用的话暗示有一些接口类型可以实现
comparable
,但是实际上根本不可能用任何接口示例化comparable
:只有方法的接口有无限的类型集,而有类型项的接口只能用作约束。2:该规则实际上没有覆盖支持
==
的非接口类型,如type S struct { data any }
,但这些类型仍然不能示例化comparable
https://go.dev/play/p/N-pmE0XC-hB,这是规范中的bug。