Go 1.19 comparison of variables of generic type

998 views
Skip to first unread message

Andrew Athan

unread,
Jan 18, 2023, 7:52:01 PM1/18/23
to golang-nuts
(Possibly related to issues such as those discussed in https://groups.google.com/g/golang-nuts/c/pO2sclKEoQs/m/5JYjveKgCQAJ ?)

If I do:

```
func Foo[V any](v V)bool {
  return v==v
}
```

golang 1.19 reports:

```
invalid operation: v == v (incomparable types in type set)
```

There are two issues with this in my mind: (1) It is ambiguous in that I cannot be sure, as a new user, whether what's being indicated is an incompatibility between the actual types on either side of the equality test or between the set of possible types that COULD be on either side of the equality test due to V's any type constraing in either the case where the left and right side are same or different types (2) In this case, the type V appears to be determinable at compile time and yet it is claimed this equality test is in some way problematic.

I'm sure I'm misunderstanding something about golang generics.

Can someone comment on this?

Thanks in advance :)

burak serdar

unread,
Jan 18, 2023, 7:59:51 PM1/18/23
to Andrew Athan, golang-nuts
The problem here is that a type constraint is different from a type, but the same identifier is overloaded in the language so "any" as a type constraint means something else from "any" as a type.

The error is there, because without that Foo([]string{}) would satisfy the constraint but it is still a compile error. Foo[V comparable)(v V) bool is the correct declaration.
 

--
You received this message because you are subscribed to the Google Groups "golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/golang-nuts/7134400e-ed5e-4c9f-bacd-4b739daf0e0bn%40googlegroups.com.

Christian Stewart

unread,
Jan 18, 2023, 8:14:53 PM1/18/23
to burak serdar, Andrew Athan, golang-nuts
The thing I ran into with this today was, if you want to compare two interfaces:

var foo1 Thing
var foo2 Thing

Ordinarily you can do foo1 == foo2 and it does pointer-wise comparison.

But in a generic function, as an example:

// IsEmpty checks if the interface is equal to nil.
func IsEmpty[T Block](blk Block) bool {
var empty T
return empty == blk
}

... that doesn't work due to the errors mentioned in this thread. And
I can't make the type constraint comparable either, that doesn't seem
to work.
> To view this discussion on the web visit https://groups.google.com/d/msgid/golang-nuts/CAMV2Rqo_nrhZfvJ_9qykTkg%3DTxbgdZMC7zFHx1rNnGcdgQk8cA%40mail.gmail.com.

Kurtis Rader

unread,
Jan 18, 2023, 8:40:47 PM1/18/23
to Christian Stewart, golang-nuts
On Wed, Jan 18, 2023 at 5:14 PM 'Christian Stewart' via golang-nuts <golan...@googlegroups.com> wrote:
The thing I ran into with this today was, if you want to compare two interfaces:

var foo1 Thing
var foo2 Thing

Ordinarily you can do foo1 == foo2 and it does pointer-wise comparison.

No, it does not. Consider the following example. Do all three vars have the same address?

    package main

    type Thing struct{}

    var foo1 Thing

    func main() {
        var foo2 Thing
        var foo3 Thing
        println(foo1 == foo2, &foo1, &foo2)
        println(foo2 == foo3, &foo2, &foo3)
    }

The output will probably surprise you. See https://go.dev/ref/spec#Comparison_operators. An empty struct is handled thusly:
  • Struct values are comparable if all their fields are comparable. Two struct values are equal if their corresponding non-blank fields are equal.
Obviously, depending on how "Thing" is defined the results may be different. Perhaps you are thinking of the case where "Thing" is an interface.



--
Kurtis Rader
Caretaker of the exceptional canines Junior and Hank

Christian Stewart

unread,
Jan 18, 2023, 9:28:16 PM1/18/23
to Kurtis Rader, Christian Stewart, golang-nuts
Kurtis,

Thing is an interface in the first example I gave:

If you want to compare two interfaces:

var foo1 Thing
var foo2 Thing

Ordinarily you can do foo1 == foo2 and it does pointer-wise comparison.

This absolutely is the case: https://gotipplay.golang.org/p/ODG9xvyVEqs

Now, I understand the confusion because I did have a major typo in the
second part of the question;

But in a generic function, as an example:

// IsEmpty checks if the interface is equal to nil.
func IsEmpty[T Block](blk T) bool {
var empty T
return empty == blk
}

(I had blk Block before, instead of blk T).

Comparing with empty is something I've needed to do a bunch of times
and have been unable to do.

Ian Lance Taylor

unread,
Jan 18, 2023, 9:56:36 PM1/18/23
to Christian Stewart, Kurtis Rader, golang-nuts
On Wed, Jan 18, 2023 at 6:28 PM 'Christian Stewart' via golang-nuts
<golan...@googlegroups.com> wrote:
>
> Thing is an interface in the first example I gave:
>
> If you want to compare two interfaces:
>
> var foo1 Thing
> var foo2 Thing
>
> Ordinarily you can do foo1 == foo2 and it does pointer-wise comparison.

It's true that if you store pointer values in a variable of interface
type that it does pointer-wise comparisons. But you can store values
of any type in a variable of interface type, not just pointer values.

https://gotipplay.golang.org/p/3swFWinr8Pb

> But in a generic function, as an example:
>
> // IsEmpty checks if the interface is equal to nil.
> func IsEmpty[T Block](blk T) bool {
> var empty T
> return empty == blk
> }
>
> (I had blk Block before, instead of blk T).
>
> Comparing with empty is something I've needed to do a bunch of times
> and have been unable to do.

The type argument to a function like IsEmpty is rarely an interface
type. So the comparison in the instantiation IsEmpty is not comparing
values of interface type. It's comparing values of whatever type
IsEmpty is instantiated with. And it is possible to instantiate
IsEmpty with types that can't be compared, even with their zero value,
such as [1][]byte.

Ian

Christian Stewart

unread,
Jan 18, 2023, 9:59:07 PM1/18/23
to Ian Lance Taylor, Christian Stewart, Kurtis Rader, golang-nuts
Hi Ian, all,

On Wed, Jan 18, 2023 at 6:56 PM Ian Lance Taylor <ia...@golang.org> wrote:
> > // IsEmpty checks if the interface is equal to nil.
> > func IsEmpty[T Block](blk T) bool {
> > var empty T
> > return empty == blk
> > }
> >
> > (I had blk Block before, instead of blk T).
> >
> > Comparing with empty is something I've needed to do a bunch of times
> > and have been unable to do.
>
> The type argument to a function like IsEmpty is rarely an interface
> type. So the comparison in the instantiation IsEmpty is not comparing
> values of interface type. It's comparing values of whatever type
> IsEmpty is instantiated with. And it is possible to instantiate
> IsEmpty with types that can't be compared, even with their zero value,
> such as [1][]byte.

This makes sense, however, is there any way to compare against empty
in this context? I was wondering how to go about doing that.

Thanks,
Christian

Ian Lance Taylor

unread,
Jan 18, 2023, 9:59:30 PM1/18/23
to Andrew Athan, golang-nuts

Ian Lance Taylor

unread,
Jan 18, 2023, 10:01:51 PM1/18/23
to Christian Stewart, Kurtis Rader, golang-nuts
You can use a constraint of "comparable". A function that uses such a
constraint can only be instantiated with type arguments that are
comparable.

Or, don't bother to use generics, and just write reflect.ValueOf(x).IsZero().

Ian

Andrew Athan

unread,
Jan 18, 2023, 11:07:41 PM1/18/23
to golang-nuts
Thank you for the practical tip on how to avoid the error. The wording of the error could be improved, IMHO, and there is a remaining issue I raised which is not addressed by the suggestion to use the "comparable" constraint (btw, the documentation I've so far been served up by duckduckgo when looking for info on generics is way too introductory, and makes no mention of things like 'comparable').

The remaining issue is that, irrespective of the "wideness" of the "any" type constraint, which apparently includes non-comparable types, the resolution of v's type is to a comparable type, yet the error is still emitted.

By the way, this is a classic case of seeing what you want to see ... my near vision is worsening and I read "comparable" in the error message as "compatible," which is at least in part why I found the error message so confusing. v==v, after all, involves two variables of the exact same type by definition :)

A.

Andrew Athan

unread,
Jan 18, 2023, 11:24:05 PM1/18/23
to golang-nuts
By the way, what is the idiomatic way to assert that a variable that is type constrained as "any" in the generic declaration, is in fact comparable (if there is a way)?

I.e., in my example "Foo" function, is there a way for me to further constrain (or rather, assert that) v's type is comparable, so that I can in fact do the comparison?

Constraining to comparable for a single function is typically not a hardship, but if you have a struct with a bunch of interface functions, most of which don't need comparable, but only one of which does, it'd be nice not to jump through declarative hoops to make that happen.

A.


On Wednesday, January 18, 2023 at 4:59:51 PM UTC-8 bse...@computer.org wrote:

Andrew Athan

unread,
Jan 18, 2023, 11:29:23 PM1/18/23
to golang-nuts
Ian:

Thanks. The example I posted is a silly uber short simplification of my usecase. In fact,  have a complex struct with a large set of methods and only one of the methods requires comparable types; it'd be nice to not have to constrain to comparable at the struct level, and only have actual uses of the one method that does need comparables fail to compile if it's called on a non-comparable instantiation, or, fall back at runtime to some acceptable behavior ... anyway: Thanks for the docs pointer. I'll read.

Ian Lance Taylor

unread,
Jan 19, 2023, 1:02:55 AM1/19/23
to Andrew Athan, golang-nuts
On Wed, Jan 18, 2023 at 8:08 PM Andrew Athan <andr...@gmail.com> wrote:
>
> The remaining issue is that, irrespective of the "wideness" of the "any" type constraint, which apparently includes non-comparable types, the resolution of v's type is to a comparable type, yet the error is still emitted.

The body of the generic function is required to compile for any type
that satisfies the constraint. That means that the compilation of a
generic function is independent of the actual type argument used to
instantiate it. This is explained at
https://go.googlesource.com/proposal/+/refs/heads/master/design/43651-type-parameters.md#constraints
.

Ian

Ian Lance Taylor

unread,
Jan 19, 2023, 1:04:50 AM1/19/23
to Andrew Athan, golang-nuts
On Wed, Jan 18, 2023 at 8:24 PM Andrew Athan <andr...@gmail.com> wrote:
>
> By the way, what is the idiomatic way to assert that a variable that is type constrained as "any" in the generic declaration, is in fact comparable (if there is a way)?
>
> I.e., in my example "Foo" function, is there a way for me to further constrain (or rather, assert that) v's type is comparable, so that I can in fact do the comparison?

There is no straightforward way to do that. You can of course do
something along those lines using the reflect package.

Ian
Reply all
Reply to author
Forward
0 new messages