Go语言泛型:Map键的类型约束?

rnmwe5a2  于 2022-12-16  发布在  Go
关注(0)|答案(1)|浏览(238)

在下面的代码中,我定义了一个通用的链表,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
}
iyr7buue

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支持操作==!= 2
  • T是接口类型,并且T的类型集中的每个类型实现comparable

即使可以比较不是类型参数的接口(可能导致运行时死机),它们也不实现comparable。
这是一个很重要的问题,因为基本接口类型通常都支持相等操作符--比较的是它们的动态类型/值。
因此,您的接口List[X]可以直接用作Map键,就像map[List[int]]string{}一样,但是它没有实现comparable,因为它有一个无限的类型集(它没有项,所以任何类型都可以实现它),并且Cons也没有实现它,因为它有一个类型为List[X]的字段,对此没有“较弱”的约束。
考虑到嵌入comparable的约束对于map键也是有效的,所以如果你真的需要函数体中的方法isList(),你可以像这样定义一个约束,并让你的lists-that-are-map-key结构实现它,而不是声明一个接口字段:

// may use this as a constraint
type List interface {
    comparable
    isList() bool
}

1:从规范中引用的话暗示有一些接口类型可以实现comparable,但是实际上根本不可能用任何接口示例化comparable:只有方法的接口有无限的类型集,而有类型项的接口只能用作约束。
2:该规则实际上没有覆盖支持==的非接口类型,如type S struct { data any },但这些类型仍然不能示例化comparablehttps://go.dev/play/p/N-pmE0XC-hB,这是规范中的bug。

相关问题