go 1.18 generic compile error when use maps.Copy on map with struct key

Issue

I implemented a Set based on generic, and everything ok until i use struct as Set element instead of base type. I got an compliation error.

go version: go version go1.18 windows/amd64

Below code is failed to complie in function AddSet.

package main

import (
    "fmt"

    "golang.org/x/exp/maps"
)

type Key struct {
    A, B int
}

func main() {
    s := SetOf(
        Key{1, 1},
        Key{2, 2},
        Key{3, 3},
    )
    s.AddSet(SetOf(
        Key{3, 3},
        Key{4, 4},
        Key{5, 5},
    ))

    fmt.Println(s)
}

type Set[T comparable] map[T]struct{}

func SetOf[T comparable](vs ...T) Set[T] {
    s := Set[T]{}
    for _, v := range vs {
        s[v] = struct{}{}
    }
    return s
}

func (s Set[T]) AddSet(another Set[T]) {
    maps.Copy(s, another)
}

when run it:

> go run .\main.go
# command-line-arguments
.\main.go:19:10: cannot use &.autotmp_29 (type *struct { A int; B int }) as type *Key in argument to runtime.mapassign
<autogenerated>:1: cannot use &.autotmp_12 (type *struct { A int; B int }) as type *Key in argument to runtime.mapassign
  • if Key only has 1 field, it can be compiled successful.
  • if i use for v := range another { s[v]=struct{}{} }, it can be compiled successful.

i think it’s strange, can someone explain please?

Solution

It looks like this compiler error. Likely it will be fixed in Go 1.19 and backported to Go 1.18.1.

For now I’d recommend simply forgoing the maps package and doing things by hand, as you already tried. It’s just a simple loop:

func (s Set[T]) AddSet(another Set[T]) {
    for k := range another {
        s[k] = struct{}{}
    }
}

@icza’s comment of explicitly converting the named map type to its underlying type also works:

maps.Copy(map[T]struct{}(s), another)

In case you use functions that expect more than one map type parameter (with the same constraints), as maps.Equal or maps.EqualFunc, you have to convert both arguments:

func (s Set[T]) Compare(another Set[T]) bool {
    // signature is Equal[M1, M2 ~map[K]V, K, V comparable](m1 M1, m2 M2) bool
    return maps.Equal(map[T]struct{}(s), map[T]struct{}(another))
}

It seems the crash is reproduced also with parametrized map types instantiated with arrays with len >= 2.

Answered By – blackgreen

Answer Checked By – Robin (GoLangFix Admin)

Leave a Reply

Your email address will not be published.