无法比较的Go结构体

比较运算符

在 Go 中比较运算符用于比较两个操作数的大小,并产生一个布尔值。

1
2
3
4
5
6
==    等于
!= 不等于
< 小于
<= 小于等于
> 大于
>= 大于等于

其中 ==!= 操作符号用于两个 comparable 的类型, 而 <, <=, >, and >= 用于比较 ordered 的类型。类型的定义如下:

类型 comparable ordered
Bool
Integer
Float-point
Complex
String
Pointer ✅指向同一个值, 或者都是 nil
Channel ✅由相同的 make 创建或者都是 nil
Interface(非泛型)
接口的实现 ✅ 非接口类型的 X 如果实现了 T,那么它实现了 T 的 t 是可比较的
Struct ✅所有的字段都是可比较的,按照顺序比较
Array ✅元素类型是可比较的
Type parameters ✅当类型为 strictly comparable

无内存分配的不可比较 Struct

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package main

import (
"fmt"
)

type nocmp [0]func()

type Foo struct {
_ nocmp
A int
}

func main() {
a := Foo{A: 1}
b := Foo{A: 2}
fmt.Println(a==b)
}

编译上面的代码会得到下面的错误信息

1
invalid operation: a == b (struct containing nocmp cannot be compared)

根据上结的规则, 结构体是否可以比较是根据字段是否可以比较的来判断的。在 Foo 中我们嵌入了一个匿名的 nocmp 类型的数据,nocmp 是一个长度为 0 的函数数组, 由于函数类型是无法比较的, 所以 nocmp 就无法比较,进一步使得 Foo 无法比较。

另外,这里使用了长度为 0 的函数数组,而不是直接定义一个函数类型的字段,如下面这段代码所示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package main

import (
"fmt"
"unsafe"
)

type nocmp [0]func()

type Foo struct {
_ nocmp
A int
}

type Bar struct {
_ func()
A int
}

func main() {
a := Foo{A: 1}
b := Bar{A: 1}
fmt.Println(unsafe.Sizeof(a))
fmt.Println(unsafe.Sizeof(b))
}

运行这段代码会得到结果

1
2
8
16

可以看到,如果直接内嵌一个函数类型的值,在初始化结构体时,就会多分配一段内存用于保存函数类型的指针。

而使用 nocmp 则不会产生额外的内存分配。

因此,当我们需要定义一个不可比较的结构体时,可以通过内嵌一个 [0]func() 类型的匿名字段,在达到目的的同时避免的内存的浪费。

参考


无法比较的Go结构体
https://blog.zhangliangliang.cc/post/go-no-compare-struct.html
作者
Bobby Zhang
发布于
2023年10月30日
许可协议