是否有一个Go语言泛型类型约束可以捕获在Map中使用类型作为键的能力?

idfiyjo8  于 2022-12-07  发布在  Go
关注(0)|答案(1)|浏览(109)

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

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

相关问题