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

本文涉及的产品
RDS MySQL DuckDB 分析主实例,集群系列 4核8GB
RDS AI 助手,专业版
简介: 在 Go 语言中,结构体默认是可以比较的,只要它们的所有字段都是可比较的。如果结构体包含不可比较的字段(如 `map`),则该结构体不能直接使用 `==` 进行比较,会引发编译错误。可以通过 `reflect.DeepEqual()` 函数对包含不可比较字段的结构体进行深度比较。为了禁止结构体的相等性比较,可以使用 `_ [0]func()` 作为匿名字段,这是一种不占用额外内存且具有明确语义的优雅做法。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 本身也在多处使用了这种用法:

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

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

相关文章
|
12月前
|
编译器 Go
揭秘 Go 语言中空结构体的强大用法
Go 语言中的空结构体 `struct{}` 不包含任何字段,不占用内存空间。它在实际编程中有多种典型用法:1) 结合 map 实现集合(set)类型;2) 与 channel 搭配用于信号通知;3) 申请超大容量的 Slice 和 Array 以节省内存;4) 作为接口实现时明确表示不关注值。此外,需要注意的是,空结构体作为字段时可能会因内存对齐原因占用额外空间。建议将空结构体放在外层结构体的第一个字段以优化内存使用。
|
9月前
|
JSON Go C语言
Go语言之定义结构体(Struct)-《Go语言实战指南》
Go 语言中的结构体(`struct`)是一种复合数据类型,可将多个不同类型的字段组合成一个类型。本文介绍了结构体的基本定义、实例创建方式、字段访问与修改、零值特性、比较规则、嵌套使用及标签功能。通过示例代码详细讲解了如何定义和操作结构体,以及其在 JSON 编码等场景的应用。
|
程序员 Go
go语言中结构体(Struct)
go语言中结构体(Struct)
381 71
|
存储 Rust Go
Go nil 空结构体 空接口有什么区别?
本文介绍了Go语言中的`nil`、空结构体和空接口的区别。`nil`是预定义的零值变量,适用于指针、管道等类型;空结构体大小为0,多个空结构体实例指向同一地址;空接口由`_type`和`data`字段组成,仅当两者均为`nil`时,空接口才为`nil`。
287 1
Go nil 空结构体 空接口有什么区别?
|
编译器 Go
探索 Go 语言中的内存对齐:为什么结构体大小会有所不同?
在 Go 语言中,内存对齐是优化内存访问速度的重要概念。通过调整数据在内存中的位置,编译器确保不同类型的数据能够高效访问。本文通过示例代码展示了两个结构体 `A` 和 `B`,尽管字段相同但排列不同,导致内存占用分别为 40 字节和 48 字节。通过分析内存布局,解释了内存对齐的原因,并提供了优化结构体字段顺序的方法,以减少内存填充,提高性能。
172 3
Go to Learn Go之结构体
Go to Learn Go之结构体
138 5
|
存储 Shell Go
Go语言结构体和元组全面解析
Go语言结构体和元组全面解析
Go: struct 结构体类型和指针【学习笔记记录】
本文是Go语言中struct结构体类型和指针的学习笔记,包括结构体的定义、成员访问、使用匿名字段,以及指针变量的声明使用、指针数组定义使用和函数传参修改值的方法。
|
存储 JSON 缓存
Go语言学习9-结构体类型
【4月更文挑战第8天】本篇 Huazie 向大家介绍 Go语言的接口类型
424 8
Go语言学习9-结构体类型
|
存储 设计模式 安全
空结构体:Go 语言中的轻量级占位符
【8月更文挑战第31天】
341 0