【开发掉坑】go 中 interface 的 nil 判断

2024-01-20 00:20
文章标签 go 开发 判断 nil interface

本文主要是介绍【开发掉坑】go 中 interface 的 nil 判断,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

今天介绍下 go 中的 interface(any)nil 判断,项目中遇到的一个小问题,知识遗忘了,再做个记录。

前言

最近在合作开发项目的过程中,发现小伙伴写了一段代码,示意代码如下:

package mainimport("encoding/json""fmt"
)type dataWrapper struct {data any
}func convert(v any) *dataWrapper {d := new(dataWrapper)d.data = vreturn 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
}func main() {var data *sureDatafmt.Println("is nil: ", data == nil) // truesd := convert(data).sureData()fmt.Println("is nil: ", sd == nil) // falseif sd == nil {// 逻辑代码} else {// 逻辑代码}
}

输出:

is nil:  true
is nil:  false

由于该代码仓库是为了让其他项目使用,基于之前的老项目抽离出来的,老项目的结构体和新项目不同,但是字段都是一样的,要进行结构体转换,偷懒用 jsonMarshalUnmarshal 来做的(这种方式不认同,对调用方不友好,而且效率还差)。

这里的 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

为什么传给 dataWrppernil 值再判断就不为 nil 了呢?按理说 sureData 得到的值应该是 nil 才对,这就引出了今天主题,判断 interface(any) 是否为 nil

原理解析

先看下 interface 的底层结构,分为两种:包含 methodiface 和不包含 methodeface,也就是 empty interface

本篇介绍基于 go1.20 版本,源码在:src/runtime/runtime2.go,具体结构如下

type iface struct {tab  *itabdata unsafe.Pointer
}type eface struct {_type *_typedata  unsafe.Pointer
}

具体的 iface, eface 结构就不在此篇介绍了。

结论:当我们判断一个 interface 的值是否为 nil 时,需要这个值的动态类型和动态值都为 nil 时,这个 interface 才会是 nil

可以看以下例子来加深印象:

package mainfunc main() {var a any = nilvar b any = (*string)(nil)println(a==nil) // trueprintln(b==nil) // false
}

解决方案

  1. 反射

注意查看 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}
}
  1. interface 底层结构

可以模拟 eface 的结构来进行 nil 判断,不建议这么使用,还是用 reflect 官方包比较好。

type eface struct {rtype unsafe.Pointerdata  unsafe.Pointer
}func IsNil(obj any) bool {return (*eface)(unsafe.Pointer(&obj)).data == nil
}
  1. 明确知道 interface 类型的值

可以使用类型断言,建议使用。

type Dog struct{}
type Cat struct{}
func IsNil(obj any) bool {switch obj.(type) {case *Dog:return obj.(*Dog) == nilcase *Cat:return obj.(*Cat) == nil}return obj == nil
}

总结

本篇以实际开发过程中的一个例子作为引导,介绍了 go 中的 interfacenil 判断的坑点。

解释了其原理:只有当类型和值都为空时,接口才为空。

参考

  • golang interface判断为空nil
  • Golang interface的类型断言是如何实现

这篇关于【开发掉坑】go 中 interface 的 nil 判断的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/624216

相关文章

Android开发中gradle下载缓慢的问题级解决方法

《Android开发中gradle下载缓慢的问题级解决方法》本文介绍了解决Android开发中Gradle下载缓慢问题的几种方法,本文给大家介绍的非常详细,感兴趣的朋友跟随小编一起看看吧... 目录一、网络环境优化二、Gradle版本与配置优化三、其他优化措施针对android开发中Gradle下载缓慢的问

使用Go语言开发一个命令行文件管理工具

《使用Go语言开发一个命令行文件管理工具》这篇文章主要为大家详细介绍了如何使用Go语言开发一款命令行文件管理工具,支持批量重命名,删除,创建,移动文件,需要的小伙伴可以了解下... 目录一、工具功能一览二、核心代码解析1. 主程序结构2. 批量重命名3. 批量删除4. 创建文件/目录5. 批量移动三、如何安

Java判断多个时间段是否重合的方法小结

《Java判断多个时间段是否重合的方法小结》这篇文章主要为大家详细介绍了Java中判断多个时间段是否重合的方法,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录判断多个时间段是否有间隔判断时间段集合是否与某时间段重合判断多个时间段是否有间隔实体类内容public class D

Android 悬浮窗开发示例((动态权限请求 | 前台服务和通知 | 悬浮窗创建 )

《Android悬浮窗开发示例((动态权限请求|前台服务和通知|悬浮窗创建)》本文介绍了Android悬浮窗的实现效果,包括动态权限请求、前台服务和通知的使用,悬浮窗权限需要动态申请并引导... 目录一、悬浮窗 动态权限请求1、动态请求权限2、悬浮窗权限说明3、检查动态权限4、申请动态权限5、权限设置完毕后

Go路由注册方法详解

《Go路由注册方法详解》Go语言中,http.NewServeMux()和http.HandleFunc()是两种不同的路由注册方式,前者创建独立的ServeMux实例,适合模块化和分层路由,灵活性高... 目录Go路由注册方法1. 路由注册的方式2. 路由器的独立性3. 灵活性4. 启动服务器的方式5.

Go语言中三种容器类型的数据结构详解

《Go语言中三种容器类型的数据结构详解》在Go语言中,有三种主要的容器类型用于存储和操作集合数据:本文主要介绍三者的使用与区别,感兴趣的小伙伴可以跟随小编一起学习一下... 目录基本概念1. 数组(Array)2. 切片(Slice)3. 映射(Map)对比总结注意事项基本概念在 Go 语言中,有三种主要

Go Mongox轻松实现MongoDB的时间字段自动填充

《GoMongox轻松实现MongoDB的时间字段自动填充》这篇文章主要为大家详细介绍了Go语言如何使用mongox库,在插入和更新数据时自动填充时间字段,从而提升开发效率并减少重复代码,需要的可以... 目录前言时间字段填充规则Mongox 的安装使用 Mongox 进行插入操作使用 Mongox 进行更

基于Python开发PPTX压缩工具

《基于Python开发PPTX压缩工具》在日常办公中,PPT文件往往因为图片过大而导致文件体积过大,不便于传输和存储,所以本文将使用Python开发一个PPTX压缩工具,需要的可以了解下... 目录引言全部代码环境准备代码结构代码实现运行结果引言在日常办公中,PPT文件往往因为图片过大而导致文件体积过大,

Go语言利用泛型封装常见的Map操作

《Go语言利用泛型封装常见的Map操作》Go语言在1.18版本中引入了泛型,这是Go语言发展的一个重要里程碑,它极大地增强了语言的表达能力和灵活性,本文将通过泛型实现封装常见的Map操作,感... 目录什么是泛型泛型解决了什么问题Go泛型基于泛型的常见Map操作代码合集总结什么是泛型泛型是一种编程范式,允

使用DeepSeek API 结合VSCode提升开发效率

《使用DeepSeekAPI结合VSCode提升开发效率》:本文主要介绍DeepSeekAPI与VisualStudioCode(VSCode)结合使用,以提升软件开发效率,具有一定的参考价值... 目录引言准备工作安装必要的 VSCode 扩展配置 DeepSeek API1. 创建 API 请求文件2.