Golang 踩坑 —— interface 为参数的时候传 nil 指针

问题

这两天踩了一个奇怪的坑,抽出核心逻辑可以得到这样一段代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
type V interface {
}

type T struct {
V
next V
}

func NewT(t V) *T {
return &T{next: t}
}

func TestName(t *testing.T) {
var tmp *T = nil
newT := NewT(tmp)
if newT.next == nil {
t.Log("newT.next is nil as expected.")
} else {
t.Errorf("newT.next should be nil, but got %v", newT.next)
}
}

此时,输出的内容是:newT.next should be nil, but got <nil>

是不是挺疑惑的,稍做修改,将 var tmp *T = nil 改成 var tmp V = nil

此时,运行得到的结果是:newT.next is nil as expected.

原因

最终在 Google Groups 上找到了相关说明:

I’m trying to understand why a nil pointer when converted to an interface produces a non-nil value.

Because different nil pointers can have different types, and the
interface remembers the type of the (nil) pointer (that it is converted from):
that remembering means that the interface value isn’t nil.

Is this a bug?

No.

(It’s a mild confusion based on the overloading of nil to mean the
zero value for pointers of any type and for interfaces — it’s not obvious
from the text of a program that the nils are of different types.)

Chris

简单来说就是为了满足类似 C++ 的 RTTI 的特性,因为转为 interface 必然会丢失掉原来的类型信息,需要保存下原来的类型

这就导致了一个具体的变量传递给一个 interface 参数的函数的时候,因为会丢失掉原始的类型,所以将其包装成一个特殊的 struct。我们可以用 unsafe 的方式来获取到相关的信息

因为样例的 interface 在 golang 中使用类似如下的结构进行存储

1
2
3
4
type eface struct {
_type *_type
data unsafe.Pointer
}

所以我们可以使用如下方案提取具体的变量值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
type InterfaceStruct struct {
pt uintptr
pv uintptr
}

type V interface {
}

type T struct {
V
next V
}

func NewT(t V) *T {
return &T{next: t}
}

func TestName(t *testing.T) {
var tmp *T = nil
newT := NewT(tmp)
pointer := *(*InterfaceStruct)(unsafe.Pointer(&newT.next))
fmt.Println(pointer)
}

就可以得到执行结果为 {4309258112 0}

也就是实际上 data 字段确实是 0,也就是 nil,但是其类型则存在一个 _type 的指针用来描述,所以在程序层面又不能说是 nil


Golang 踩坑 —— interface 为参数的时候传 nil 指针
https://blog.mauve.icu/2024/07/28/golang/interface-nil-param/
作者
Shiroha
发布于
2024年7月28日
许可协议