怎么让 Go 中如何让结构体不可比较?

科技公元 后端 2024-06-21

怎么让 Go 中如何让结构体不可比较?

在 Go 中结构体可以比较吗?

在 Go 中结构体可以比较吗?这其实是我曾经面试过的一个问题,我们来做一个实验:

定义如下结构体:go

复制代码
type Normal struct { a string B int }

使用这个结构体分别声明 3 个变量 n1n2n3,然后进行比较:go

复制代码
n1 := Normal{ a: "a", B: 10, } n2 := Normal{ a: "a", B: 10, } n3 := Normal{ a: "b", B: 20, } fmt.Println(n1 == n2) fmt.Println(n1 == n3)

执行示例代码,输出结果如下:bash

复制代码
$ go run main.go true false

可见 Normal 结构体是可以比较的。

如何让结构体不可比较?

那么所有结构体都可以比较吗?显然不是,如果都可以比较,那么 reflect.DeepEqual() 就没有存在的必要了。

定义如下结构体:go

复制代码
type NoCompare struct { a string B map[string]int }

使用这个结构体分别声明 2 个变量 n1n2,然后进行比较:go

复制代码
n1 := NoCompare{ a: "a", B: map[string]int{ "a": 10, }, } n2 := NoCompare{ a: "a", B: map[string]int{ "a": 10, }, } fmt.Println(n1 == n2)

执行示例代码,输出结果如下:bash

复制代码
$ go run main.go ./main.go:59:15: invalid operation: n1 == n2 (struct containing map[string]int cannot be compared)

这里程序直接报错了,并提示结构体包含了 map[string]int 类型字段,不可比较。

所以小结一下:

结构体是否可以比较,不取决于字段是否可导出,而是取决于其是否包含不可比较字段。

如果全部字段都是可比较的,那么这个结构体就是可比较的。

如果其中有一个字段不可比较,那么这个结构体就是不可比较的。

不过虽然我们不可以使用 ==n1n2 进行比较,但我们可以使用 reflect.DeepEqual() 对二者进行比较:go

复制代码
fmt.Println(reflect.DeepEqual(n1, n2))

执行示例代码,输出结果如下:bash

复制代码
$ go run main.go true

更优雅的做法

最近我在使用 Go 官方出品的结构化日志包 slog 时,看到 slog.Value 源码:go

复制代码
// A Value can represent any Go value, but unlike type any, // it can represent most small values without an allocation. // The zero Value corresponds to nil. type Value struct { _ [0]func() // disallow == // num holds the value for Kinds Int64, Uint64, Float64, Bool and Duration, // the string length for KindString, and nanoseconds since the epoch for KindTime. num uint64 // If any is of type Kind, then the value is in num as described above. // If any is of type *time.Location, then the Kind is Time and time.Time value // can be constructed from the Unix nanos in num and the location (monotonic time // is not preserved). // If any is of type stringptr, then the Kind is String and the string value // consists of the length in num and the pointer in any. // Otherwise, the Kind is Any and any is the value. // (This implies that Attrs cannot store values of type Kind, *time.Location // or stringptr.) any any }

可以发现,这里有一个匿名字段 _ [0]func(),并且注释写着 // disallow ==

_ [0]func() 的目的显然是为了禁止比较。

我们来实验一下,_ [0]func() 是否能够实现禁止结构体相等性比较:go

复制代码
v1 := Value{ num: 1, any: 2, } v2 := Value{ num: 1, any: 2, } fmt.Println(v1 == v2)

执行示例代码,输出结果如下:bash

复制代码
$ go run main.go ./main.go:109:15: invalid operation: v1 == v2 (struct containing [0]func() cannot be compared)

可以发现,的确有效。因为 func() 是一个函数,而函数在 Go 中是不可比较的。

既然使用 map[string]int_ [0]func() 都能实现禁止结构体相等性比较,那么我为什么说 _ [0]func() 是更优雅的做法呢?

_ [0]func() 有着比其他实现方式更优的特点:

它不占内存空间!

使用匿名字段 _ 语义也更强。

而且,我们直接去 Go 源码里搜索,能够发现其实 Go 本身也在多处使用了这种用法:

怎么让 Go 中如何让结构体不可比较?

所以推荐使用 _ [0]func() 来实现禁用结构体相等性比较。

转载来源:https://juejin.cn/post/7381396887189569575

Apipost 私有化火热进行中

评论