Go 泛型:映射键的类型约束?

Mik*_*tay 4 generics go comparable type-constraints

在下面的代码中,我定义了一个通用链表。Go1.18 很乐意使用列表的实例作为映射的键。但是,最后一行如果未注释,则无法编译;我收到错误:

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
}
Run Code Online (Sandbox Code Playgroud)

bla*_*een 5

Go 1.20(2023 年 2 月)

\n

comparable是映射键的正确的包罗万象的约束。

\n

所有按照 Go 规范进行比较的类型,即使比较可能在运行时发生恐慌,也可以满足comparable约束。您的代码将在 1.20 中按预期进行编译。

\n

这最终修复了以前的 Go 版本中关于规范可比较类型与comparable类型的不一致问题。详情请参阅下文。

\n

转到 1.18 和 1.19

\n

预先声明的comparable约束是映射键的正确约束,但是它只能通过严格可比较的类型实例化,即支持==and !=(用作映射键的条件)但在运行时不会恐慌的类型。这不包括接口1。

\n

这里提到了这一点: https: //go.dev/ref/spec#Type_constraints

\n
\n

预先声明的接口类型可比较表示所有可比较的非接口类型的集合。具体来说,类型 T\n在以下情况下实现可比较:

\n
    \n
  • T不是接口类型,T支持操作==和!= 2
  • \n
  • T是一个接口类型,并且T\的类型集中的每个类型都实现comparable
  • \n
\n

即使不是类型参数的接口可以进行比较(可能导致运行时恐慌),它们也不会实现可比较。

\n
\n

这是一个重要的问题,因为基本接口类型通常支持相等运算符\xe2\x80\x94 比较的是它们的动态类型/值。

\n

因此,您的接口List[X]可以直接用作映射键,如 中所示map[List[int]]string{},但它没有实现comparable,因为它有一个无限类型集(它没有术语,因此任何类型都实现它)。并且Cons\xe2\x80\x99t 也不会实现它,因为它有一个类型为 的字段List[X]。对此没有“较弱”的约束。

\n

考虑到嵌入的约束comparable对于映射键也有效,所以如果您确实需要函数体中的方法isList(),您可以定义这样的约束,并让您的列表是映射键结构实现它,而不是声明接口字段:

\n
// may use this as a constraint\ntype List interface {\n    comparable\n    isList() bool\n}\n
Run Code Online (Sandbox Code Playgroud)\n
\n

1:规范中的引用暗示存在实现 的接口类型comparable,但实际上根本不可能用comparable任何接口实例化:仅具有方法的接口具有无限类型集,并且具有类型术语的接口不能除了作为约束之外,可以在任何地方使用。

\n

2:该规则实际上不涵盖支持 的非接口类型==,例如type S struct { data any },但这些类型仍然无法实例化comparable https://go.dev/play/p/N-pmE0XC-hB。这是规范中的一个错误。

\n