今天介绍下 go
中的 interface(any)
的 nil
判断,项目中遇到的一个小问题,知识遗忘了,再做个记录。
前言
最近在合作开发项目的过程中,发现小伙伴写了一段代码,示意代码如下:
package main
import(
"encoding/json"
"fmt"
)
type dataWrapper struct {
data any
}
func convert(v any) *dataWrapper {
d := new(dataWrapper)
d.data = v
return d
}
type sureData struct {
Name string
}
func (d *dataWrapper) sureData() *sureData {
buf, _ := json.Marshal(d.data)
data := new(sureData)
json.Unmarshal(buf, data)
return data
}
type oldData struct {
Name string
}
func main() {
var data *oldData
fmt.Println("is nil: ", data == nil) // true
sd := convert(data).sureData()
fmt.Println("is nil: ", sd == nil) // false
if sd == nil {
// 逻辑代码
} else {
// 逻辑代码
}
}
输出:
is nil: true
is nil: false
由于该代码仓库是为了让其他项目使用,基于之前的老项目抽离出来的,老项目的结构体和新项目不同,但是字段都是一样的,要进行结构体转换,偷懒用 json
的 Marshal
和 Unmarshal
来做的(这种方式不认同,对调用方不友好,而且效率还差)。
这里的 dataWrapper
就是进行结构体转换的一个封装,最终使用 sureData
方法获取真正的结构体数据。
代码简单,可以看到这里的 sureData
方法获取的数据肯定不为空,因为它在方法里做了 new(sureData)
了,返回的结构体肯定不为空。
代码看到这里,想要使其能正确地判断 nil
,对 sureData
方法进行了如下修改(当然只是做示例用,真实场景中不推荐):
func (d *dataWrapper) sureData() *sureData {
if d.data == nil {
return nil
}
buf, _ := json.Marshal(d.data)
data := new(sureData)
json.Unmarshal(buf, data)
return data
}
但是运行查看输出结果和刚刚没区别
is nil: true
is nil: false
为什么传给 dataWrpper
的 nil
值再判断就不为 nil
了呢?按理说 sureData
得到的值应该是 nil
才对,这就引出了今天主题,判断 interface(any)
是否为 nil
。
原理解析
先看下 interface
的底层结构,分为两种:包含 method
的 iface
和不包含 method
的 eface
,也就是 empty interface
。
本篇介绍基于 go1.20
版本,源码在:src/runtime/runtime2.go
,具体结构如下
type iface struct {
tab *itab
data unsafe.Pointer
}
type eface struct {
_type *_type
data unsafe.Pointer
}
具体的 iface
, eface
结构就不在此篇介绍了。
结论:当我们判断一个 interface
的值是否为 nil
时,需要这个值的动态类型和动态值都为 nil
时,这个 interface
才会是 nil
。
可以看以下例子来加深印象:
package main
func main() {
var a any = nil
var b any = (*string)(nil)
println(a==nil) // true
println(b==nil) // false
}
解决方案
- 反射
注意查看 nil
的定义(源码:src/builtin/builtin.go
),使用反射进行 nil
判断时需要注意类型只能是 pointer, channel, func, interface, map, or slice type
,使用其他类型会直接 panic
。
// nil is a predeclared identifier representing the zero value for a
// pointer, channel, func, interface, map, or slice type.
var nil Type // Type must be a pointer, channel, func, interface, map, or slice type
func IsNil(v any) bool {
valueOf := reflect.ValueOf(v)
k := valueOf.Kind()
switch k {
case reflect.Chan, reflect.Func, reflect.Map, reflect.Ptr, reflect.UnsafePointer, reflect.Interface, reflect.Slice:
return valueOf.IsNil()
default:
return v == nil
}
}
- interface 底层结构
可以模拟 eface
的结构来进行 nil
判断,不建议这么使用,还是用 reflect
官方包比较好。
type eface struct {
rtype unsafe.Pointer
data unsafe.Pointer
}
func IsNil(obj any) bool {
return (*eface)(unsafe.Pointer(&obj)).data == nil
}
- 明确知道
interface
类型的值
可以使用类型断言,建议使用。
type Dog struct{}
type Cat struct{}
func IsNil(obj any) bool {
switch obj.(type) {
case *Dog:
return obj.(*Dog) == nil
case *Cat:
return obj.(*Cat) == nil
}
return obj == nil
}
总结
本篇以实际开发过程中的一个例子作为引导,介绍了 go
中的 interface
的 nil
判断的坑点。文章来源:https://www.toymoban.com/news/detail-808622.html
解释了其原理:只有当类型和值都为空时,接口才为空。文章来源地址https://www.toymoban.com/news/detail-808622.html
参考
- golang interface判断为空nil
- Golang interface的类型断言是如何实现
到了这里,关于【开发掉坑】go 中 interface 的 nil 判断的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!