Golang 中 nil==nil 是对是错?

简介: Golang 中 nil==nil 是对是错?

这篇文章,我们将了解如何在 Go 中使用 == 操作符比较对象值。我们还将进一步研究某些场景下,该操作符的行为看起来像是一个错误,可实际是因为缺乏理解导致的。

看看下面的例子。

var a *string = nil    
var b interface{} = a
fmt.Println("a == nil:", a == nil) // true 
fmt.Println("b == nil:", b == nil) // false 
fmt.Println("a == b:", a == b  ) // true

结果对你来说奇怪吗?

为了理解上面的例子,我们从一个简单的例子开始。

var a int = 12
var b int = 12
c:= 12
fmt.Println("a == b :", a == b) // true
fmt.Println("a == c :", a == c) // true

这里的答案不难理解吧。

我们来改动一下这个例子。

var a *string = nil
var b interface{} = nil
fmt.Println("a == nil:", a == nil) // true
fmt.Println("b == nil:", b == nil) // true
fmt.Println("a == b:", a == b)     // false (尽管a和b的值都为nil)

为了理解最后一种情况,我们深入研究一下。

在 Go 中,每个指针变量都有两个与之相关的值,<type,value>。事实上,每个变量都必须指定一个类型,这就是为什么我们不能给未定义的变量赋空值。这就是为什么我们不能直接写 x:=ni 的原因,如果我们没有定义 x 的类型,我们会得到以下错误:

use of untyped nil

重新看这个问题,

var a *string = nil      // a is <*string, nil>
var b interface{} = nil  // b is <nil, nil>
fmt.Println("a == nil:", a == nil) // true
fmt.Println("b == nil:", b == nil) // true
fmt.Println("a == b:", a == b)    // false <*string,nil>!=<nil, nil>

示例中,变量 a 实际表示 <*string,nil>。

interface{} 默认类型是 nil,因此变量 b 实际是 <nil,nil>。

在判断 a==b 时,实际上是在比较 <*string,nil> == <nil,nil>,当然是 false。

现在回到我们第一个例子,

var a *string = nil     // a is <*string, nil>
var b interface{} = a
fmt.Println("a == nil:", a == nil) //true (<*int, nil>==<*int, nil>)
fmt.Println("b == nil:", b == nil) //false (<*string,nil>==<nil,nil>    
fmt.Println("a == b:", a == b) // true

当判断 a==nil 时,== 运算符比较类型和值。在这里,左边和右边的值都是 nil,但是 nil 的类型是什么?

当 nil (硬编码的值)与对象比较时,nil 的类型和与它比较的对象声明的类型相同。

因此,在判断 a==nil 的情况下,本质上是判断 <*string,nil> ==<*string,nil>。所以,结果为 true。

当判断 b==nil 时,右边实际上 <nil,nil>,而左边 b 的类型是 interface{},它的默认值是 nil。但是当我们做了,

var b interface{} = a

我们将 a 的值赋值给了 b,这就意味着 b 现在引用了 <*string,nil>。因此 当判断 b==nil 时,答案为 false。

注意:var b interface{} =a 不改变 b 变量的声明类型。


总结


这个问题在开发过程中经常出现,例如下面这段代码,我们可能会遇到这样的错误。

var resp string
var err error
resp, err = CallFunction()
if err != nil{
   // 如果 CallFunction 返回的类型和 err 不匹配,则执行
}
func CallFunction() custom.Error{
 // 返回指针变量
}

这里有更多的实验[1]

因此,最好检查下被调用函数的返回类型和调用函数中接收变量的类型是否匹配。


附录


[1]https://play.golang.org/p/BnPPXEs9_Oq

相关文章
|
11月前
|
Go
golang踩坑 3.接口值是否等于nil
golang踩坑 3.接口值是否等于nil
|
数据库连接 Go
[Golang] runtime error: invalid memory address or nil pointer dereferenc报错
[Golang] runtime error: invalid memory address or nil pointer dereferenc报错
|
Java 编译器 Go
Golang在func中分配的变量通过参数传递出函数域之后变nil的问题
Golang在func中分配的变量通过参数传递出函数域之后变nil的问题,其实就是一个变量逃逸的问题。
347 0
|
1月前
|
监控 算法 Go
Golang深入浅出之-Go语言中的服务熔断、降级与限流策略
【5月更文挑战第4天】本文探讨了分布式系统中保障稳定性的重要策略:服务熔断、降级和限流。服务熔断通过快速失败和暂停故障服务调用来保护系统;服务降级在压力大时提供有限功能以保持整体可用性;限流控制访问频率,防止过载。文中列举了常见问题、解决方案,并提供了Go语言实现示例。合理应用这些策略能增强系统韧性和可用性。
122 0
|
1月前
|
前端开发 Go
Golang深入浅出之-Go语言中的异步编程与Future/Promise模式
【5月更文挑战第3天】Go语言通过goroutines和channels实现异步编程,虽无内置Future/Promise,但可借助其特性模拟。本文探讨了如何使用channel实现Future模式,提供了异步获取URL内容长度的示例,并警示了Channel泄漏、错误处理和并发控制等常见问题。为避免这些问题,建议显式关闭channel、使用context.Context、并发控制机制及有效传播错误。理解并应用这些技巧能提升Go语言异步编程的效率和健壮性。
65 5
Golang深入浅出之-Go语言中的异步编程与Future/Promise模式
|
1月前
|
Prometheus 监控 Cloud Native
Golang深入浅出之-Go语言中的分布式追踪与监控系统集成
【5月更文挑战第4天】本文探讨了Go语言中分布式追踪与监控的重要性,包括追踪的三个核心组件和监控系统集成。常见问题有追踪数据丢失、性能开销和监控指标不当。解决策略涉及使用OpenTracing或OpenTelemetry协议、采样策略以及聚焦关键指标。文中提供了OpenTelemetry和Prometheus的Go代码示例,强调全面可观测性对微服务架构的意义,并提示选择合适工具和策略以确保系统稳定高效。
165 5
|
1月前
|
监控 负载均衡 算法
Golang深入浅出之-Go语言中的协程池设计与实现
【5月更文挑战第3天】本文探讨了Go语言中的协程池设计,用于管理goroutine并优化并发性能。协程池通过限制同时运行的goroutine数量防止资源耗尽,包括任务队列和工作协程两部分。基本实现思路涉及使用channel作为任务队列,固定数量的工作协程处理任务。文章还列举了一个简单的协程池实现示例,并讨论了常见问题如任务队列溢出、协程泄露和任务调度不均,提出了解决方案。通过合理设置缓冲区大小、确保资源释放、优化任务调度以及监控与调试,可以避免这些问题,提升系统性能和稳定性。
65 6
|
1月前
|
负载均衡 算法 Go
Golang深入浅出之-Go语言中的服务注册与发现机制
【5月更文挑战第4天】本文探讨了Go语言中服务注册与发现的关键原理和实践,包括服务注册、心跳机制、一致性问题和负载均衡策略。示例代码演示了使用Consul进行服务注册和客户端发现服务的实现。在实际应用中,需要解决心跳失效、注册信息一致性和服务负载均衡等问题,以确保微服务架构的稳定性和效率。
37 3
|
1月前
|
安全 Go
Golang深入浅出之-Go语言中的并发安全队列:实现与应用
【5月更文挑战第3天】本文探讨了Go语言中的并发安全队列,它是构建高性能并发系统的基础。文章介绍了两种实现方法:1) 使用`sync.Mutex`保护的简单队列,通过加锁解锁确保数据一致性;2) 使用通道(Channel)实现无锁队列,天生并发安全。同时,文中列举了并发编程中常见的死锁、数据竞争和通道阻塞问题,并给出了避免这些问题的策略,如明确锁边界、使用带缓冲通道、优雅处理关闭以及利用Go标准库。
42 5