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))
}
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?
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)
}