案例一:
type Base interface {
do()
}
type App struct {
}
func (a *App) do() {}
func GetApp() *App {
return nil
}
func GetApp2() Base {
var app *App
return app
}
func GetApp3() *App {
return nil
}
func TestGetApp(t *testing.T) {
var base Base
base = GetApp()
fmt.Println("GetApp()1", base) // <nil>
fmt.Println("GetApp()1", base == nil) // false
base2 := GetApp2()
fmt.Println("GetApp()2", base2) // <nil>
fmt.Println("GetApp()2", base2 == nil) // false
var base3 *App
base3 = GetApp3()
fmt.Println("GetApp()3", base3) // <nil>
fmt.Println("GetApp()3", base3 == nil) // true
// 因为GetApp()3返回值只是一个普通的指针结构体,不是接口Base
}
Go底层对于非空接口的定义,使用 iface
结构体来定义:
type iface struct {
tab *itab
data unsafe.Pointer
}
// itab 结构体定义
type itab struct {
inter *interfacetype // 接口自身的元信息
_type *_type // 具体类型的元信息
link *itab
bad int32
hash int32 // _type里也有一个同样的hash,此处多放一个是为了方便运行接口断言
fun [1]uintptr // 函数指针,指向具体类型所实现的方法
}
app 是一个指向nil的空指针,但是最后return app 会触发匿名变量 App = app值拷贝动作
所以最后GetApp2() 返回给上层的是一个 Base insterface{}类型,也就是一个iface struct{}类型。
app为nil,只是 iface 中的data 为nil而已。 但是iface struct{}本身并不为nil.
因为iface
结构体中tab *itab
字段是有值的,具体包含了:
_type
表示具体化的类型,有值,具体内容指向:type App struct{}
fun
表示具体类型所实现的方法,有值,具体内容指向:func (a *App) do() {}
但是 data unsafe.Pointer
字段是 nil: 为*App = nil
案例二:空接口判断nil
func Foo(x interface{}) {
if x == nil {
fmt.Println("empty interface")
return
}
fmt.Println("non-empty interface")
}
func TestFoo(t *testing.T) {
var p *int = nil
Foo(p) // non-empty interface
}
Foo()的形参x interface{}是一个空接口类型eface struct{}。
空接口定义:
type eface struct {
_type *_type // 类型信息
data unsafe.Pointer // 指向数据的指针(go语言中特殊的指针类型unsafe.Pointer类似于c语言中的void*)
}
在执行Foo(p)的时候,触发x interface{} = p语句,所以此时 x结构如下。
_type *type
表示数据类型的描述,字段有值,指向*int
data unsafe.Pointer
表示具体的数据类型或者说实现类,值为nil:p = nil
所以 x 结构体本身不为 nil,而是 data指 针指向的p为nil。
**案例三:error 接口为nil
**
type MyErr struct {
Msg string
}
func GetErr() *MyErr {
return nil
}
func (m *MyErr) Error() string {
return ""
}
func TestGetErr(t *testing.T) {
var e error
e = GetErr()
fmt.Println(e == nil) // false
}
背后的原因本质上还是对 Go 语言中 interface 的基本原理的理解。
在案例三中,虽然 GetErr 方法确实是返回了 nil,返回的类型也是具体的 *MyErr
类型。但是其接收的变量却不是具体的结构类型,而是 error 类型:
var e error
e = GetErr()
在 Go 语言中, error 类型本质上是 interface:
type error interface {
Error() string
}
因此兜兜转转又回到了 interface 类型的问题,interface 不是单纯的值,而是分为类型和值。
所以传统认知的此 nil 并非彼 nil,必须得类型和值同时都为 nil 的情况下,interface 的 nil 判断才会为 true。
在案例三中,结合代码逻辑,更符合场景的是:
var e *MyErr
e = GetErr()
log.Println(e == nil)
输出结果就会是 true。
在案例二中,也是一样的结果,原因也是 interface。不管是 error 接口(interface),还是自定义的接口,背后原理一致,自然也就结果一致了。