В приведенном ниже коде я определяю общий связанный список. Go1.18 с удовольствием использует экземпляр списка в качестве ключа к карте. Однако последняя строка, если она не закомментирована, не компилируется; Я получаю сообщение об ошибке:
Cons[int] does not implement comparable
Есть ли более слабое ограничение типа, которое я могу использовать, чтобы выбрать те типы, которые можно использовать в качестве ключей, или это предназначено, или это ошибка компилятора?
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
}
Предварительно объявленное ограничение comparable
является правильным универсальным ограничением для ключей карты, поскольку оно реализовано типами, поддерживающими ==
и !=
(условие использования в качестве ключей карты), но не интерфейсы 1.
Это упоминается здесь: https://go.dev/ref/spec#Type_constraints
The predeclared interface type comparable denotes the set of all non-interface types that are comparable. Specifically, a type T implements comparable if:
T
is not an interface type andT
supports the operations==
and!=
T
is an interface type and each type inT
's type set implementscomparable
Это важный нюанс, потому что базовые типы интерфейсов обычно поддерживают операторы равенства — сравниваются их динамические типы/значения.
Следовательно, ваш интерфейс List[X]
можно использовать как ключ карты напрямую, как в map[List[int]]string{}
, но он не реализует comparable
, потому что имеет бесконечный набор типов (у него нет термов, поэтому его реализует любой тип). И Cons
тоже не реализует, потому что имеет поле типа List[X]
. Для этого нет более слабого ограничения.
Учтите, что ограничения, которые встраивают comparable
, также действительны для ключей карты, поэтому, если вам действительно нужен метод isList()
в теле функции, вы можете определить ограничение, подобное этому, и реализовать его в ваших структурах списков, которые являются ключами карты, вместо объявления поля интерфейса:
// may use this as a constraint
type List interface {
comparable
isList() bool
}
1: the quote from the specs hints there are interface types that implement comparable
, but it's effectively not possible to instantiate comparable
with any interface at all: interfaces with only methods have an infinite type set, and interfaces with type terms can't be used anywhere except as constraints.