In the following example foo
and bar
are basically of the same type: map[uint32]string
.
Nevertheless go1.18beta complains that: M2 does not match map[K]V
.
Is it even possible to get equal
to accept both of these maps? Do I need to change the signature of equal
or the declarations of the maps itself?
package main
import "fmt"
func equal[M1, M2 ~map[K]V, K, V comparable](m1 M1, m2 M2) bool {
if len(m1) != len(m2) {
return false
}
for k, v1 := range m1 {
if v2, ok := m2[k]; !ok || v1 != v2 {
return false
}
}
return true
}
type (
someNumericID uint32
someStringID string
)
func main() {
foo := map[uint32]string{
10: "bar",
}
bar := map[someNumericID]someStringID{
10: "bar",
}
if equal(foo, bar) == true {
fmt.Println("Maps are the same")
} else {
fmt.Println("Maps are not the same")
}
}
CodePudding user response:
Is it even possible to get equal to accept both of these maps?
Yes, but you have to differentiate the key and value types, as they are not the same. This is what the fixed function could look like:
func equal[K1, K2 ~uint32, V1, V2 ~string](m1 map[K1]V1, m2 map[K2]V2) bool {
if len(m1) != len(m2) {
return false
}
for k, v1 := range m1 {
if v2, ok := m2[K2(k)]; !ok || V2(v1) != v2 {
return false
}
}
return true
}
In particular, both keys and value type parameters are constrained to the respective approximate elements ~uint32
and ~string
in order to allow the conversions m2[K2(k)]
and V2(v1)
in the function body. This is required to compare values (including map indexing) that don't have the same type, but have the same underlying type.
The above solution forgoes type parameters M1
and M2
on the map types — due to what appears to be a compiler bug; see comments for details —, but since you are not actually using those types in the function body, nor in return values, they are not strictly needed.
Playground: https://gotipplay.golang.org/p/Y8C_8ilsXUg
If you want to understand why your first example failed, here's a breakdown. The relevant paragraph in the language specs is Type inference.
- In
equal[M1, M2 ~map[K]V, K, V comparable](m1 M1, m2 M2)
, the type paramsM1
andM2
have the same constraint~map[K]V
. - When you call the function without explicit instantiation, the compiler tries to infer the type parameters from the types of the supplied arguments. In short, it infers
K
andV
fromM1
, soequal(foo, bar)
wherefoo
ismap[uint32]string
results inK = uint32
andV = string
. - The instantiated constraint is then
M1, M2 ~map[uint32]string
- Now there's no more type params to infer, so it just type-checks
bar
against the instantiated constraint. Is the underlying (~
) type ofbar
the same asmap[uint32]string
? No. Even if the underlying types of keys and vals are the same, the underlying type of the entire map is exactlymap[someNumericID]someStringID
. - Instantiation of
equal
with argsfoo
andbar
fails.
This becomes more apparent if instead of relying on type inference, you instantiate equal
with explicit type args. By specifying just M1
and M2
(remember that they have the same constraint): equal[map[uint32]string, map[uint32]string](foo, bar)
then bar
obviously doesn't match.