0%

Go语言interface的坑

以下代码执行结果是什么

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package main

import "fmt"

type Namespace interface {
Value() string
}

type MyNamespace struct {
value string
}

func (n *MyNamespace) Value() string {
return n.value
}

func show(n Namespace) {
if n != nil {
fmt.Println(n.Value())
} else {
fmt.Println("Hello World!")
}
}

func main() {
var n *MyNamespace = nil
show(n)
}

结果是报错。

1
2
3
4
5
6
7
8
9
10
11
12
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0xffffffff addr=0x0 pc=0xe01c6]

goroutine 1 [running]:
main.(*MyNamespace).Value(0x0, 0x5bf2, 0x119bf8, 0x1d)
/tmp/sandbox404522411/prog.go:16 +0x6
main.show(0x15d910, 0x0)
/tmp/sandbox404522411/prog.go:21 +0x40
main.main()
/tmp/sandbox404522411/prog.go:30 +0x40

Program exited.

报一个空指针错误。n != nil的判断在这里无效。

原因是Go语言中,interface{}类型的变量包含了2个指针,一个指针指向值的类型,一个指针指向实际的值。MyNamespace实现了Namespace接口,n在传入Show(n)函数时被自动转型成为Namespacevar n *MyNameSpace = nil只是声明了该接口类型的实际值为nil,虽然我们把一个nil值赋值给它,但是实际上interface里依然存了指向类型的指针,所以拿这个interface变量去和nil常量进行比较的话就会返回false

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

func show(n Namespace) {
fmt.Printf("👉👉 %p\n", n)
x := *(*asStruct)(unsafe.Pointer(&n))
spew.Dump(x)
spew.Println("n == nil ?", n == nil)
if n != nil && !reflect.ValueOf(n).IsNil() {
fmt.Println(n.Value())
} else {
fmt.Println("Hello World!")
}
}

// Output:
// 👉👉 0x0
// (main.asStruct) {
// pt: (uintptr) 0x1125f80,
// pv: (uintptr) <nil>
// }
// n == nil ? false
// Hello World!

我们看到传入的参数指针值为0,但interface{}类型的变量的两个指针,值的类型是有值的,实际值为nil。因此接口传入的不是实际的nil的情况下,n==nil就为false。可以使用reflect.ValueOf(n).IsNil()来判断是否实际值为nil。

reflect.ValueOf只接受指针类型,否则会panic

如果显式传入show(nil)interface{}类型变量的两个指针都为nil,则n == nil判断生效。

1
2
3
4
5
6
//show(nil)
(main.asStruct) {
pt: (uintptr) <nil>,
pv: (uintptr) <nil>
}
n == nil ? true

参考

Go语言第一深坑 - interface 与 nil 的比较

golang中interface判断nil问题