Golang中使用maps.Copy时遇到结构体作为键的编译错误问题

Golang中使用maps.Copy时遇到结构体作为键的编译错误问题 我基于泛型实现了一个Set,当使用基础类型作为Set元素时一切正常,但当我使用结构体作为Set元素时,出现了编译错误。

go版本:go version go1.18 windows/amd64

以下代码在函数 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)
}

运行时:

> 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
  • 如果 Key 结构体只有一个字段,则可以成功编译。
  • 如果我使用 for v := range another { s[v]=struct{}{} },则可以成功编译。

我觉得这很奇怪,有人能解释一下吗?我也在Stack Overflow上提了一个问题:go 1.18 generic compile error when use maps.Copy on map with struct key - Stack Overflow


更多关于Golang中使用maps.Copy时遇到结构体作为键的编译错误问题的实战教程也可以访问 https://www.itying.com/category-94-b0.html

2 回复

blackgreen 已经给出了一个很棒的答案,请查看指向 Stackoverflow 问题的链接。

但我不知道是否有办法手动关闭这个主题。

更多关于Golang中使用maps.Copy时遇到结构体作为键的编译错误问题的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


这个编译错误是由于Go 1.18中泛型实现的一个已知问题导致的。当结构体作为comparable类型键时,maps.Copy函数在处理某些结构体类型时会产生类型推断问题。

问题出现在maps.Copy的泛型实现中,它无法正确处理包含多个字段的结构体作为map键的情况。这是一个编译器层面的bug,在后续的Go版本中已经修复。

解决方案:使用显式的循环代替maps.Copy

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

或者使用类型断言来绕过编译器问题

func (s Set[T]) AddSet(another Set[T]) {
    // 将map转换为interface{}再处理
    src := map[T]struct{}(another)
    dst := map[T]struct{}(s)
    
    for k := range src {
        dst[k] = struct{}{}
    }
}

完整的工作示例:

package main

import (
    "fmt"
)

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) // 输出: map[{1 1}:{} {2 2}:{} {3 3}:{} {4 4}:{} {5 5}:{}]
}

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]) {
    for v := range another {
        s[v] = struct{}{}
    }
}

根本原因分析:

这个bug与Go编译器的类型推断机制在处理泛型结构体键时的内部实现有关。当结构体包含多个字段时,编译器在生成中间代码时会产生类型不匹配的错误。这个问题在Go 1.19及更高版本中已经得到修复。

验证修复:

// Go 1.19+ 中可以正常使用maps.Copy
package main

import (
    "fmt"
    "golang.org/x/exp/maps"
)

type Key struct {
    A, B, C int // 多个字段在1.19+中也能正常工作
}

func main() {
    s1 := map[Key]string{
        {1, 2, 3}: "value1",
    }
    s2 := map[Key]string{
        {4, 5, 6}: "value2",
    }
    
    maps.Copy(s1, s2)
    fmt.Println(s1) // 输出: map[{1 2 3}:value1 {4 5 6}:value2]
}

建议升级到Go 1.19或更高版本以获得完整的泛型支持修复。如果必须使用Go 1.18,请使用显式循环作为替代方案。

回到顶部