go: spec: `any` no longer implements `comparable`

What version of Go are you using (go version)?

$ go version
go version go1.18rc1 darwin/arm64

Does this issue reproduce with the latest release?

Yes

What operating system and processor architecture are you using (go env)?

go env Output
$ go env
GO111MODULE=""
GOARCH="arm64"
GOBIN=""
GOCACHE="/Users/jb/Library/Caches/go-build"
GOENV="/Users/jb/Library/Application Support/go/env"
GOEXE=""
GOEXPERIMENT=""
GOFLAGS=""
GOHOSTARCH="arm64"
GOHOSTOS="darwin"
GOINSECURE=""
GOMODCACHE="/Users/jb/go/pkg/mod"
GONOPROXY=""
GONOSUMDB=""
GOOS="darwin"
GOPATH="/Users/jb/go"
GOPRIVATE=""
GOPROXY="https://proxy.golang.org,direct"
GOROOT="/opt/homebrew/Cellar/go/1.18rc1/libexec"
GOSUMDB="sum.golang.org"
GOTMPDIR=""
GOTOOLDIR="/opt/homebrew/Cellar/go/1.18rc1/libexec/pkg/tool/darwin_arm64"
GOVCS=""
GOVERSION="go1.18rc1"
GCCGO="gccgo"
AR="ar"
CC="clang"
CXX="clang++"
CGO_ENABLED="1"
GOMOD="/Users/jb/code/golang-set/go.mod"
GOWORK=""
CGO_CFLAGS="-g -O2"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-g -O2"
CGO_FFLAGS="-g -O2"
CGO_LDFLAGS="-g -O2"
PKG_CONFIG="pkg-config"
GOGCCFLAGS="-fPIC -arch arm64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fdebug-prefix-map=/var/folders/qv/ll7pql3n7dbf5w8rf46bcttm0000gn/T/go-build4282451061=/tmp/go-build -gno-record-gcc-switches -fno-common"

What did you do?

https://go.dev/play/p/jML-W5X8dks?v=gotip

What did you expect to see?

Program exited.

(no error)

What did you see instead?

./prog.go:16:17: any does not implement comparable

Go build failed.

Alternatively, using comparable rather than any, this error:

./prog.go:16:20: interface is (or embeds) comparable

Go build failed.

Ref: https://github.com/golang/go/issues/50646#issuecomment-1044754308

I understand that this seems to be intended, but I think it’s going to make it practically quite difficult for developers to use generics in Go - as far as I can tell, there is simply no way to make this work

I think this should be reconsidered, or at least discussed more widely - as it does not seem to be specified in the proposal

This seems significant enough to be considered as a release blocker

A library which is affected in practice: https://github.com/deckarep/golang-set/issues/79 (using its generics branch) - I have been trying to find workarounds to allow for this new behaviour, but have not managed to resolve it

About this issue

  • Original URL
  • State: closed
  • Created 2 years ago
  • Reactions: 3
  • Comments: 16 (12 by maintainers)

Most upvoted comments

I think this issue should have gone through the proposal process. The type parameters design doc explicitly states:

The type set of the comparable constraint is the set of all comparable types.

The definition of “comparable” at the time that proposal was approved states (emphasis mine):

Interface values are comparable. Two interface values are equal if they have identical dynamic types and equal dynamic values or if both have value nil.

By those definitions, every interface type should be included in the type-set of comparable, and thus any — as an interface type itself — should be in that type-set.

Redefining comparable not to include interface types is a very late and drastic departure from the design that was approved in #43651.

I believe this is just a bug: When we check if a type implements comparable, we must always use the same criteria for comparability. In this case:

type T struct{ x any }

func main() {
	NewSetFromSlice([]T{
		{"foo"},
		{5},
	})
}

the type T is not an interface and the code is using the traditional (before 1.18) predicate to determine if T is comparable. It should be using an updated version.

I think this needs to be fixed for 1.18 otherwise we’ll have to maintain this inconsistency.

I agree that the long-term solution is to allow comparable as an ordinary type. See also my comment here:

Had we introduced a notation (some special mark) for interfaces for which we expect to use == and != in the beginning of Go, all this would have been a non-issue: clearly one wouldn’t be able to compare two interfaces that don’t support comparison. Such a mark would also have made it impossible to assign a non-comparable value to non-comparable interfaces, etc. Instead we relied on a run-time test, at considerable implementation cost, for that matter. This is something we cannot change. But generic code will now be type-safe with respect to this.

@billinghamj Note that Set is declared as follows:

type Set[T comparable] map[T]struct{}

A Set is a map, and maps are not comparable, they have never been. So you can’t use a Set (i.e., a map) as a key for another map. This is also not possible in non-generic Go.

FWIW I am not sure that []comparable really fixes the inconsistency. For example this works today:

type T struct{ x any }

func main() {
	NewSetFromSlice([]T{
		{"foo"},
		{5},
	})
}

It is odd that []T (where T contains any) is allowed but []any is not. It feels like an inconsistent cut-out. But I completely agree about waiting until after Go 1.18 to figure this out.

Is there any reason to believe we can’t relax this in a future release? Being conservative for now seems like the best answer.

Notably, if interfaces do not satisfy comparable, then a transition like this for sync.Map doesn’t work:

type MapOf[K comparable, V any] struct { ... }
type Map = MapOf[any, any]