You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Go cmp:如何为以结构体为键的Map定义自定义相等性?

问题描述

现有如下Go代码,定义了包含指针字段的MapKey结构体作为map的键:

type Dog struct {
    Name string
}

type Cat struct {
    Name string
}

type MapKey struct {
    dog *Dog
    cat *Cat
}

myMap := make(map[MapKey]int)

创建两个mapgot和want,其中的键keyOne和keyTwo是不同的MapKey实例,但它们指向的Dog、Cat结构体值完全相同:

keyOne := MapKey{
    &Dog{Name: "bob"},
    &Cat{Name: "clive"},
}
keyTwo := MapKey{
    &Dog{Name: "bob"},
    &Cat{Name: "clive"},
}

got := map[MapKey]int{
    keyOne: 1,
}
want := map[MapKey]int{
    keyTwo: 1,
}

希望使用cmp包判定这两个map相等,但默认的cmp.Equal会认为两个map不相等——因为它默认比较指针的地址而非指向的结构体内容。尝试为MapKey定义自定义Equals函数未生效,也未理解cmpopts.SortMaps与当前场景的关联。

解决方案

方法1:使用cmp.Comparer自定义键的比较逻辑

cmp.Comparer允许你为特定类型定义自定义的相等判定规则。针对MapKey,我们可以编写比较器,忽略指针地址,转而比较指针指向的结构体内容:

package main

import (
    "fmt"
    "github.com/google/go-cmp/cmp"
)

type Dog struct {
    Name string
}

type Cat struct {
    Name string
}

type MapKey struct {
    dog *Dog
    cat *Cat
}

func main() {
    keyOne := MapKey{
        &Dog{Name: "bob"},
        &Cat{Name: "clive"},
    }
    keyTwo := MapKey{
        &Dog{Name: "bob"},
        &Cat{Name: "clive"},
    }

    got := map[MapKey]int{keyOne: 1}
    want := map[MapKey]int{keyTwo: 1}

    // 定义MapKey的自定义比较器
    mapKeyComparer := cmp.Comparer(func(a, b MapKey) bool {
        // 处理指针为nil的情况,再比较结构体内容
        dogEqual := (a.dog == nil && b.dog == nil) || 
                    (a.dog != nil && b.dog != nil && a.dog.Name == b.dog.Name)
        catEqual := (a.cat == nil && b.cat == nil) || 
                    (a.cat != nil && b.cat != nil && a.cat.Name == b.cat.Name)
        return dogEqual && catEqual
    })

    // 使用自定义比较器执行相等判定
    if cmp.Equal(got, want, mapKeyComparer) {
        fmt.Println("Maps are equal")
    } else {
        fmt.Println("Maps are not equal")
    }
}

方法2:让MapKey实现cmp.Equaler接口

如果希望MapKey类型默认使用自定义相等逻辑,可以让它实现cmp.Equaler接口(注意接口要求的参数类型是interface{}):

package main

import (
    "fmt"
    "github.com/google/go-cmp/cmp"
)

type Dog struct {
    Name string
}

type Cat struct {
    Name string
}

type MapKey struct {
    dog *Dog
    cat *Cat
}

// 实现cmp.Equaler接口
func (mk MapKey) Equal(x interface{}) bool {
    other, ok := x.(MapKey)
    if !ok {
        return false
    }
    dogEqual := (mk.dog == nil && other.dog == nil) || 
                (mk.dog != nil && other.dog != nil && mk.dog.Name == other.dog.Name)
    catEqual := (mk.cat == nil && other.cat == nil) || 
                (mk.cat != nil && other.cat != nil && mk.cat.Name == other.cat.Name)
    return dogEqual && catEqual
}

func main() {
    keyOne := MapKey{
        &Dog{Name: "bob"},
        &Cat{Name: "clive"},
    }
    keyTwo := MapKey{
        &Dog{Name: "bob"},
        &Cat{Name: "clive"},
    }

    got := map[MapKey]int{keyOne: 1}
    want := map[MapKey]int{keyTwo: 1}

    // 直接调用cmp.Equal,会自动使用MapKey的Equal方法
    if cmp.Equal(got, want) {
        fmt.Println("Maps are equal")
    } else {
        fmt.Println("Maps are not equal")
    }
}

补充说明

  • cmpopts.SortMaps的作用是比较map时忽略键的顺序,和当前场景无关——你的问题是键被判定为不同,而非键的顺序导致的不相等。
  • 之前自定义的Equals函数未生效,是因为cmp包不会自动识别任意命名的相等方法,必须通过cmp.Comparer选项,或者实现cmp.Equaler标准接口。

内容的提问来源于stack exchange,提问作者Charlie Clarke

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.13 19:05:29