这个:
是 Go 里面:
最经典的坑之一。
几乎:
txt
所有 Go 新人
都会踩
甚至:
很多工作几年的人:
都容易写错。
因为:
真正难点:
不是 error。
而是:
txt
interface 底层机制
今天:
我们彻底讲透。
一、error 到底是什么
很多人:
以为:
error 是特殊类型。
其实不是。
它本质:
就是:
go
interface
源码:
go
type error interface {
Error() string
}
二、什么意思
只要一个类型:
实现了:
go
Error() string
它就自动实现:
go
error 接口
三、例如
go
type MyError struct{}
现在:
给它实现:
go
func (e *MyError) Error() string {
return "出错了"
}
四、这一刻发生了什么(重点)
因为:
go
*MyError
拥有:
go
Error() string
所以:
Go 自动认为:
txt
*MyError 实现了 error 接口
五、于是就能这样写
go
var err error
err = &MyError{}
六、为什么不需要 implements
Go:
接口实现:
是:
txt
隐式实现
不是:
txt
Java 那种显式 implements
七、真正核心(重点)
Go:
只看:
txt
你有没有这个方法
有:
就算实现接口。
八、现在来到最经典坑(重点)
代码
go
func foo() error {
var p *MyError = nil
return p
}
九、main
go
func main() {
err := foo()
fmt.Println(err == nil)
}
十、很多人以为输出什么
很多人:
会觉得:
txt
true
因为:
go
p == nil
十一、但真正输出
txt
false
十二、为什么(真正核心)
因为:
txt
error 是 interface
而:
txt
interface 底层
=
(Type, Value)
十三、interface 底层结构(必须理解)
interface:
实际上:
像:
txt
(T, V)
T
类型。
V
值。
十四、例如
go
var x interface{} = 123
底层:
txt
T = int
V = 123
十五、再看你的代码
go
var p *MyError = nil
这里:
txt
T = *MyError
V = nil
十六、return p 时发生什么(重点)
因为:
函数返回值:
是:
go
error
所以:
Go:
会把:
go
p
装进:
txt
interface
十七、于是 err 实际变成
txt
err =
(
T = *MyError,
V = nil
)
十八、重点来了(超级重要)
真正的:
txt
nil interface
必须:
txt
T = nil
V = nil
十九、而现在
你的:
txt
T = *MyError
并不是 nil。
二十、所以整个 interface 不等于 nil
于是:
go
err == nil
结果:
txt
false
二十一、真正底层图(重点)
真正 nil interface
txt
interface
├── T = nil
└── V = nil
你的 err
txt
interface
├── T = *MyError
└── V = nil
二十二、所以为什么叫"假 nil"
因为:
txt
值是 nil
但:
txt
接口本身不是 nil
二十三、这是 Go 面试超级经典题
因为:
很多人:
根本不知道:
txt
interface 有类型信息
二十四、真正危险在哪里
例如:
错误代码
go
func foo() error {
var p *MyError = nil
return p
}
调用方
go
err := foo()
if err != nil {
fmt.Println("发生错误")
}
二十五、会发生什么
明明:
txt
没有错误
却:
进入:
txt
发生错误
逻辑。
二十六、这就是线上 BUG 来源
因为:
开发者:
以为:
txt
返回的是 nil
实际上:
不是。
二十七、正确写法(重点)
正确方式
go
func foo() error {
var p *MyError = nil
if p == nil {
return nil
}
return p
}
二十八、为什么这样就对了
因为:
这里:
go
return nil
返回的是:
txt
真正的 nil interface
也就是:
txt
T = nil
V = nil
二十九、于是
go
err == nil
结果:
才是:
txt
true
三十、真正核心理解(重点)
错误写法
go
return p
返回:
txt
(*MyError, nil)
正确写法
go
return nil
返回:
txt
(nil, nil)
三十一、为什么 Go 要这样设计
因为:
interface:
必须:
知道:
txt
里面装的是什么类型
否则:
Go:
无法调用:
go
Error()
三十二、真正本质一句话(重点)
interface:
永远:
保存:
txt
动态类型
+
动态值
三十三、再举个经典例子
go
var p *int = nil
var x interface{} = p
fmt.Println(x == nil)
三十四、输出
txt
false
三十五、原因完全一样
因为:
txt
x =
(
T = *int,
V = nil
)
三十六、Go 官方最经典一句话
很多 Go 老手:
都会说:
txt
带类型的 nil != nil interface
三十七、最后一句总结(必须记住)
error:
本质:
就是:
go
interface
interface 底层:
txt
(Type, Value)
真正 nil interface
txt
(T=nil, V=nil)
假 nil
txt
(T=*MyError, V=nil)
因为:
txt
类型不为空
所以:
go
err != nil
真正核心:
txt
带类型的 nil
不等于
空 interface