惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

小众软件
小众软件
B
Blog RSS Feed
美团技术团队
博客园 - 【当耐特】
C
Check Point Blog
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
M
MIT News - Artificial intelligence
aimingoo的专栏
aimingoo的专栏
J
Java Code Geeks
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
Microsoft Azure Blog
Microsoft Azure Blog
博客园 - 司徒正美
T
Tailwind CSS Blog
Last Week in AI
Last Week in AI
Google DeepMind News
Google DeepMind News
D
DataBreaches.Net
人人都是产品经理
人人都是产品经理
N
Netflix TechBlog - Medium
Vercel News
Vercel News
P
Proofpoint News Feed
IT之家
IT之家
I
InfoQ
腾讯CDC
H
Hackread – Cybersecurity News, Data Breaches, AI and More

OhYee 博客

小鹏辅助驾驶测评|OhYee 博客 小鹏非支持手机开启自动解锁|OhYee 博客 使用函数计算实现 301 重定向|OhYee 博客 针对 HTML 内容使用 Ant Design 图片弹框|OhYee 博客 博客进程泄露及僵尸进程解决|OhYee 博客 蓝易云服务器体验|OhYee 博客 SSH 调起本地 VSCode|OhYee 博客 【2022 秋招内推】阿里云后端研发工程师|OhYee 博客 使用函数计算获取 IP 地址信息|OhYee 博客 正确获取客户端 IP/HTTP Header 也可能重复|OhYee 博客 评测 Oculus Quest2 及 BigScreen|OhYee 博客 NextJS 热重载保留状态|OhYee 博客 如何优雅地贴 gist 代码|OhYee 博客 Linux 精细化文件权限|OhYee 博客 VSCode 容器开发环境|OhYee 博客 Clash 的不兼容更新排查|OhYee 博客 Zeek 导出 PCAP|OhYee 博客 记一次 ssh 配置问题|OhYee 博客 Git Commit 规范化工具|OhYee 博客 谈谈《星之卡比-探索发现》|OhYee 博客 VSCode 快捷键绑定 Shell 命令|OhYee 博客 ASN.1 语法及 X.509 证书格式解析解析|OhYee 博客 腾讯企业邮箱忽略 MX 记录发信|OhYee 博客 Chrome/Edge 标签组插件|OhYee 博客 【应届内推】阿里云后端研发工程师|OhYee 博客 损坏的 Typecho 备份处理为 JSON|OhYee 博客 VS Code VIM 插件高效使用|OhYee 博客 SSH 正反向代理|OhYee 博客 Let's Encrypt 根证书过期引发的问题|OhYee 博客 OpenWRT 忽略内核依赖|OhYee 博客
Go 中值为 nil 的 interface|OhYee 博客
2021-11-06 · via OhYee 博客

Go 中值为 nil 的 interface

该知识点在之前的博客中曾经提到过,但是我仍然义无反顾踩了坑。排查耗时: 2 天(中间穿插了其他事情)

Go nil 定义

nil 不是一个关键字,你甚至可以 var nil = 1(就像 #define true false 一样),可以将所有 Go 的变量理解成一个 {Type, Value} 的数据。那么自然 nil 也是存在类型的。
不同类型的 nil 不同,运行时会自动把代码中的 nil 替换为与它比较的变量类型的 nil,也即

var a *int
fmt.Println(a == nil) // fmt.Println(a == (*int)(nil))

易犯的错误

那么,如果程序无法推测出类型、或者推测类型错误,比较结果自然也是错误的

可以看下面的程序

package main

import (
	"fmt"
)

type AnimalInterface interface {
	Eat()
}

type DogInterface interface {
	AnimalInterface
	Woof()
}

type Husky struct{}

func (dog *Husky) Eat()  {}
func (dog *Husky) Woof() { fmt.Println("Woof!") }

func main() {
	var animal AnimalInterface = nil
	var dog DogInterface = nil
	var husky *Husky = nil
	fmt.Printf("%#v %#v %#v %#v %#v %#v\n", animal, dog, husky, animal != nil, dog != nil, husky != nil)

	animal = (*Husky)(nil)
	dog = (*Husky)(nil)
	husky = (*Husky)(nil)
	fmt.Printf("%#v %#v %#v %#v %#v %#v\n", animal, dog, husky, animal != nil, dog != nil, husky != nil)

	animal = (DogInterface)(nil)
	dog = (DogInterface)(nil)
	fmt.Printf("%#v %#v %#v %#v %#v %#v\n", animal, dog, husky, animal != nil, dog != nil, husky != nil)

	animal = new(Husky)
	dog = new(Husky)
	husky = new(Husky)
	fmt.Printf("%#v %#v %#v %#v %#v %#v\n", animal, dog, husky, animal != nil, dog != nil, husky != nil)
}

该代码编译器会提示 Warning,但是如果套娃几层后可能会被编译器忽视

这段代码的输出为

<nil> <nil> (*main.Husky)(nil) false false false
(*main.Husky)(nil) (*main.Husky)(nil) (*main.Husky)(nil) true true false
<nil> <nil> (*main.Husky)(nil) false false false
&main.Husky{} &main.Husky{} &main.Husky{} true true true

可以观察到,interface{} 实际上是 没有类型

第一部分由于都是 nil,因此会被推断为变量对应的类型,获得正确的比较结果
而第二行由于 animaldog 都被赋值了 (*Husky)(nil),而他们本身的类型推断为 (nil)(nil),因此在比较结果时返回的是 true(因为 Husky 符合 Animal 和 Dog 的约束,因此是可以赋值成功的。
而由于 interface{} 类型都是 nil,因此第三行正确
第四行都被赋值,不属于 nil

实际项目中可能的错误

虽然看起来该问题很容易理解,但是仍然属于一不留神就会犯的错误。

考虑如下场景

函数内需要检查传入的参数是否合法,对于值为 nil 的参数,应该执行特殊的初始化逻辑。被传入的函数需要根据别的条件来确定是否存在,因此先初始化为 nil 的对应结构体,再根据需要对其赋值
一个类似的 DEMO 如下

func run(r *io.Reader) {
    if r == nil {
        return
    }
    r.Read()
}

func Run(hasReader bool) {
    var r *bytes.Buffer = nil
    if hasReader {
        r = new(bytes.Buffer)
    }
    run(r)
}

虽然看上去明确赋值 r = nil,而且在不需要时避免初始化无用的结构体,节省了内存。看上去一切美好,但是 r 只会等于 (*bytes.Buffer)(nil) 永远不会不会等于 nil。很容易就会发生空指针异常。

因此很多函数在处理时,会直接返回 nil 来避免出错,如

func do() (*Object, error) {
    // ...
    if err != nil {
        return nil, err
    }

    return &Object{}, nil
}

在这里,尽管最后 return &Object, err 结果上也是返回了一个为 nil 的错误,但由于 error 本身是一个 interface{},因此外层判断时可能会出错。故,必须直接返回 nil

结论

如果返回值是 nil,那么直接写 nil,而非某个结构体指针(即使这个指针目前指向 nil

类似地,尽可能避免使用指向 nil 的结构体指针,取而代之使用对应的指向 nilinterface{}

参考资料