What does the comparable constraint mean in generics, and what changed in Go 1.20?
Question 27HardGo 1.22 to 1.25
comparable is a built-in constraint that permits == and != on a type parameter, so it can be used as a map key:
func Index[T comparable](s []T, v T) int {
for i, x := range s {
if x == v {
return i
}
}
return -1
}
func Set[K comparable](keys ...K) map[K]struct{} {
m := make(map[K]struct{}, len(keys))
for _, k := range keys {
m[k] = struct{}{}
}
return m
}
_ = Index([]any{1, "a"}, "a") // Go 1.20+: compiles
_ = Index([]any{[]int{1}}, any([]int{1})) // compiles, but panics at run time
Before Go 1.20, comparable accepted only strictly comparable types, meaning types whose == can never panic. So any and other interface types couldn't satisfy it, and you couldn't write Set[any]. Go 1.20 relaxed this. Interfaces, and structs or arrays containing interfaces, now satisfy comparable, even though comparing them may panic at run time. This matches how == already behaves outside generics.
Also note:
comparablecan only be used as a constraint, not as a regular variable type.comparabledoes not allow<. For ordering, usecmp.Ordered(Go 1.21).- Only strictly comparable types implement
comparable, a distinction that matters when writing your own constraints.
More on Language Fundamentals & Types
- Q25Which types are comparable in Go, and when does comparing values panic at run time?
- Q26How does struct and array equality work? What is surprising about NaN as a map key?
- Q28Explain method sets. Why does
var s Speaker = Dog{}fail to compile? - Q29What is addressability, and why can't you call a pointer method on a map element?
- Q30How does struct embedding affect method sets and name resolution?
- Q31Why is an interface holding a nil pointer not equal to nil? What does this print?