Go 里的 error 接口 + 假 nil(超级重点)

这个:

是 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
相关推荐
愿天垂怜1 小时前
【C++脚手架】ffmpeg 库的介绍与使用
linux·服务器·开发语言·c++·ide·git·ffmpeg
并不喜欢吃鱼1 小时前
从零开始 C++-----十一【C++ 数据结构】红黑树全解析:从定义到工程实现(一文搞定,十分详细)
开发语言·数据结构·c++
不会C语言的男孩1 小时前
C++ Primer Plus 第7章:函数——C++的编程模块
开发语言·c++
方也_arkling1 小时前
【Java-Day09】继承
java·开发语言
西安邮电大学1 小时前
Kafka保证消息顺序性
java·后端·kafka
迈巴赫车主1 小时前
蓝桥杯21247弹跳鞋java
java·开发语言·数据结构·算法·职场和发展·蓝桥杯
贺国亚1 小时前
RAG 检索增强 · 向量库与 Chunking
后端·面试
SoftLipaRZC2 小时前
C语言数据在内存中的存储:整型与浮点型的秘密
c语言·开发语言
悟乙己2 小时前
python DoWhy 库使用案例: SaaS 公司的客服案例
开发语言·python