7

I have tried to use generics with Go, but I don't really understand when we use any or comparable as type parameter. Can someone help to understand these?

blackgreen
  • 34,072
  • 23
  • 111
  • 129
Teguh
  • 81
  • 4

2 Answers2

11

It depends on what / how you want to use values of the parameter type. Constraints restrict what you can do with values of those types.

any is an alias for interface{} which allows any type. If a parameter can be of any type, that basically won't allow you to do anything with it because you have no guarantee what it will be.

The comparable constraints only allows types that are comparable, that is, the == and != operators are allowed to use on values of them. This is good if you want to use the type as a key in a map (maps require key types to be comparable), or if you you want to find an element in a slice, and you want to use the == operator to compare the elements to something.

As an example, let's write a generic map-get function:

func get[K comparable, V any](m map[K]V, key K) V {
    return m[key]
}

The K key type must be comparable, else it cannot be used as the key type of some map (m[K]V in the example). V on the other hand shouldn't be constrained, the value type may be anything, and we're not doing anything with it (just returning a value of type V), so using any here is the best choice.

Another example, a slice-find function:

func find[V comparable](what V, s []V) int {
    for i, v := range s {
        if v == what {
            return i
        }
    }
    return -1
}

find() returns the index of the first occurrence of what in the slice s, and if it's not in the slice, returns -1. The type parameter V here must be comparable, else you couldn't write v == what, using V any would be a compile-time error. The constraint comparable ensures this find() function can only be instantiated with types (and called with values) where the == operator is defined and allowed.

icza
  • 389,944
  • 63
  • 907
  • 827
  • So, `comparable` is good for `map`. I will try that to see what happen there. Btw, Thank @icza for the explanation. – Teguh Mar 18 '22 at 08:16
  • 2
    @Teguh Yes, it's good for map keys, and it's good also for anything where you must use the `==` and/or `!=` operators. – icza Mar 18 '22 at 08:27
0

The difference between comparable and any will change with Go 1.20 (Q1 2023) and (accepted) the proposal "56548: spec: allow basic interface types to instantiate comparable type parameters".

any will implement the comparable constraint (which it does not before Go 1.20).

Go 1.20-rc1 states:

Comparable types (such as ordinary interfaces) may now satisfy comparable constraints, even if the type arguments are not strictly comparable (comparison may panic at runtime).

This makes it possible to instantiate a type parameter constrained by comparable (e.g., a type parameter for a user-defined generic map key) with a non-strictly comparable type argument such as an interface type, or a composite type containing an interface type.


The principle is:

After substitution, each type argument must satisfy the constraint (instantiated, if necessary) of the corresponding type parameter. Otherwise instantiation fails.

With "satisfy" being:

A type T satisfies a constraint interface C if

  • T implements C; or
  • C can be written in the form interface{ comparable; E }, where E is a basic interface and T is comparable and implements E.

Example:

Currently, any does not implement the comparable constraint.

With the proposed change any will be permitted as type argument for comparable: comparable can be written as interface{ comparable; E } and thus the new rule applies, and any is spec-comparable and implements E (where E is the empty interface in this case).

Currently, the type parameter P in the type parameter list

[P interface{ comparable; fmt.Stringer }]

cannot be instantiated with the type S

type S struct {
  data any
}

func (S) String() string

because S is not strictly comparable.
With the proposed change, S must only be spec-comparable (which it is) and implement fmt.Stringer (which it does).

("spec-comparable" are for types of comparable operands)
(as opposed to "strictly comparable", which is for the types in comparable, namely the set of (non-interface) types for which == and != are defined and for which these operations are guaranteed to not panic)

The implementation as started:

  • CL 453979: "cmd/compile: enable new comparable semantics by default"
  • CL 453978: "go/types, types2: make the new comparable semantics the default"
VonC
  • 1,262,500
  • 529
  • 4,410
  • 5,250
  • *This will change* — what, will change? To be very honest, I don't think this answer should be posted under this question; icza's answer doesn't even mention the strictly comparable limitation. And I don't think it should be posted this early, since the Go specs haven't been amended yet. As of today, you are quoting non-normative references. – blackgreen Dec 02 '22 at 19:03