gogenericsinterface

Using generic interface for structs with pointer receivers that are passed by value in Go


I have a bunch of types that implements a Equal(other myType) method for comparison against other values of the same type.

These types are used as map values and I wish to compare map equality using maps.EqualFunc

For various reasons, they Equal function must be a pointer receiver method on the types, as some types have methods to modify the internals of the struct and I want to pass them around as values.

I want to define a generic EqualFunc for maps.EqualFunc to easily compare map values.

This is currently my implementation:

package main

import (
    "fmt"
    "maps"
)

type myValue struct {
    v string
}

func (v *myValue) Equal(other myValue) bool {
    return v.v == other.v
}

type Equaler[T any] interface {
    Equal(T) bool
}

func mapEqualFunc[V Equaler[V]](a, b V) bool {
    return a.Equal(b)
}

func main() {
    myMap1 := map[string]myValue{
        "one": myValue{v: "one"},
        "two": myValue{v: "two"},
    }

    myMap2 := map[string]myValue{
        "one":   myValue{v: "one"},
        "two":   myValue{v: "two"},
        "three": myValue{v: "three"},
    }

    fmt.Println(maps.EqualFunc(myMap1, myMap2, mapEqualFunc))
}

Playground link

The problem is that attemping to build fails with the following message:

./prog.go:36:28: in call to maps.EqualFunc, V (type myValue) does not satisfy Equaler[V] (method Equal has pointer receiver)

I can get it to work if I change the Equal() method to use a value receiver, but for the reasons outlined above, this is not something I want to do.

Is it possible to have generics accept both pointer-receiver and value-receiver methods for satisfying the interface?


Solution

  • You can update mapEqualFunc to define an interface (i.e. generic constraint) which is constrained to both Equaler[V] and as a pointer *V. You can then use a type conversion to safely apply this constraint.

    func mapEqualFunc[V any, PV interface {
        Equaler[V]
        *V
    }](a, b V) bool {
        return PV(&a).Equal(b)
    }
    

    Here the method passed to EqualFunc still keeps the signature of accepting two value types a, b V, and the generic constraint PV makes it explicit that the pointer *V will still implement the interface for Equaler[V].

    If you want to make this even more flexible, say to allow something like:

    func (v *myValue1) Equal(other myValue2) bool
    

    Then you can define a and b as separate generic args and have the constraint apply to only a. e.g.

    func mapEqualFunc[A any, B any, PA interface {
        Equaler[B]
        *A
    }](a A, b B) bool {
        return PA(&a).Equal(b)
    }