在下面的代码中,我定义了一个通用的链表。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条答案
按热度按时间oyxsuwqo1#
Go 1.20(2023年2月)
comparable
是Map键的正确捕获所有约束。所有符合Go语言规范的可比较类型,即使比较在运行时可能会发生混乱,也都可以满足
comparable
约束。您的代码将按照预期在1.20中编译。这最终修正了Go语言上一版本中关于规范可比类型和
comparable
类型的不一致。执行1.18和1.19
预声明的
comparable
约束是Map键的正确约束,但是它只能由严格可比的类型示例化,即支持==
和!=
(用作Map键的条件)但不会在运行时混乱的类型。这排除了interfaces 1。这是这里提到的:https://go.dev/ref/spec#Type_constraints
预声明的接口类型comparable表示所有可比较的非接口类型的集合。特别地,类型T实现comparable如果:
T
不是接口类型,并且T
支持操作==
和!=
2T
是一个接口类型,并且T
的类型集中的每个类型都实现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。