7

Consider the following code snippet for version go1.18beta2 linux/amd64

    type Vector[T comparable] struct {
       data_ []T
    }
    
    func (v *Vector[T]) Contains(e T) bool {
       for _, x := range v.data_ {
          if x == e {
             return true
          }
       }
       return false
    }
    
    func TestVector(t *testing.T) {
       v2 := Vector[Vector[int]]{}
    }

This does not compile and gives error: “Vector[int] does not implement comparable” simply because Vector has no equality operators defined. However, I cannot find how to define them.

Question: Is this approach of creating a comparable struct not allowed, and why; or is the documentation not written yet?

0

1 Answer 1

10

The constraint comparable is predeclared and supported by the language specifications. You can't "manually" make a type implement it. The documentation is available in the specs (under Type Constraints):

The predeclared interface type comparable denotes the set of all concrete (non-interface) types that are comparable. Specifically, a type T implements comparable if:

  • T is not an interface type and T supports the operations == and !=; or
  • T is an interface type and each type in T's type set implements comparable.

Your type Vector[T comparable] doesn't meet any of those conditions. It is not an interface type, and it does not otherwise support the equality operations, because one of its fields data_ []T is not comparable due to being a slice — even if element type is constrained by comparable.

The purpose of the comparable constraint is really just to allow writing generic code with == and != operators. If a type is not comparable by design, you can't write such code. That would be true even if Vector didn't have a type parameter.

If your goal is instantiating Vector[Vector[T]] and allow equality tests between instances of Vector[T], you might want to add an Equal method that takes care of this specific use case — only vectors instantiated with the same type parameter as the receiver will be allowed:

func (v *Vector[T]) Equal(e Vector[T]) bool {
    // test equality in a way that makes sense for this type
}

Worth mentioning that there is a way to make Vector[T comparable] comparable itself, i.e. change the data_ field to be a pointer-to-slice:

type Vector[T comparable] struct {
    data_ *[]T
}

Now instantiation with Vector[Vector[int]] compiles. However, beside being very cumbersome to initialize with struct literals (playground), it comes with all caveats of pointer comparison. More specifically:

Two pointer values are equal if they point to the same variable or if both have value nil. Pointers to distinct zero-size variables may or may not be equal.

Now the comparison x == e tests that the memory address stored in the data_ field in x and e is the same. This might skew the semantics of comparing two Vector[T] instances — is it correct to say that two vector instances are equal if they hold a reference to the same slice? Maybe. It depends on the assumptions your program wants to make. Personally, I don't think this is actually better than having a separate Equal method and/or redesigning your data types, but as usual, YMMV.

Note also that if you instantiate as Vector[float64] and compare NaN values, the comparison will be false.

Sign up to request clarification or add additional context in comments.

3 Comments

This example illustrates my main motivation to use generics. And I'm trying to cope with the slight disappointment. Or is there another method to create comparable structs (that are resizable such as slices)?
@HJLebbink right now I’m not aware of a cleaner design. One might be tempted to use comparable in unions or type assertions, but that’s not possible.. If a type is not comparable by design, it has to be handled differently, e.g. by implementing Equal methods etc… Note that arrays are comparable~ but have fixed length
@HJLebbink you can make Vector[T] comparable by declaring the field as a pointer, like data_ *[]T. Pointers as per the specs quoted above are comparable. However I'm not sure it's preferable because 1) initialization and usage become more cumbersome and verbose, 2) pointer comparison tests whether the mem address is the same, so x == e where T is another Vector[T] instance basically checks if the two structs hold the same exact slice pointer. YMMV. Example gotipplay.golang.org/p/zSx2mjLmptz

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.