以下代码执行结果是什么
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 mainimport "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 (0 x0, 0 x5bf2, 0 x119bf8, 0 x1d) /tmp/sandbox404522411/prog.go :16 +0 x6 main .show (0 x15d910, 0 x0) /tmp/sandbox404522411/prog.go :21 +0 x40 main .main () /tmp/sandbox404522411/prog.go :30 +0 x40 Program exited.
报一个空指针错误。n != nil的判断在这里无效。
原因是Go语言中,interface{}类型的变量包含了2个指针,一个指针指向值的类型,一个指针指向实际的值。MyNamespace实现了Namespace接口,n在传入Show(n)函数时被自动转型成为Namespace。var 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!" ) } }
我们看到传入的参数指针值为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 (main.asStruct) { pt: (uintptr ) <nil >, pv: (uintptr ) <nil > } n == nil ? true
参考 Go语言第一深坑 - interface 与 nil 的比较
golang中interface判断nil问题