golang: 詳解interface和nil

golang的nil在概念上和其它語言的null、None、nil、NULL同樣,都指代零值或空值。nil是預先說明的標識符,也即一般意義上的關鍵字。在golang中,nil只能賦值給指針、channel、func、interface、map或slice類型的變量。若是未遵循這個規則,則會引起panic。對此官方有明確的說明:http://pkg.golang.org/pkg/builtin/#Typehtml

golang中的interface相似於java的interface、PHP的interface或C++的純虛基類。接口就是一個協議,規定了一組成員。這個沒什麼好說的,本文不打算對宏觀上的接口概念和基於接口的範式編程作剖析。golang語言的接口有其獨到之處:只要類型T的公開方法徹底知足接口I的要求,就能夠把類型T的對象用在須要接口I的地方。這種作法的學名叫作Structural Typing,有人也把它看做是一種靜態的Duck Typing。所謂類型T的公開方法徹底知足接口I的要求,也便是類型T實現了接口I所規定的一組成員。java

在底層,interface做爲兩個成員來實現,一個類型和一個值。對此官方也有文檔說明:http://golang.org/doc/go_faq.html#nil_error,若是您不習慣看英文,這裏有一篇柴大的翻譯:Go中error類型的nil值和nil 。golang

接下來經過編寫測試代碼和gdb來看看interface倒底是什麼。會用到反射,若是您不太瞭解golang的反射是什麼,這裏有刑星翻譯自官方博客的一篇文章:反射的規則,原文在:laws-of-reflectionshell

$GOPATH/src編程

----interface_test函數

--------main.go測試

main.go的代碼以下:ui

package main

import (
	"fmt"
	"reflect"
)

func main() {
	var val interface{} = int64(58)
	fmt.Println(reflect.TypeOf(val))
	val = 50
	fmt.Println(reflect.TypeOf(val))
}

咱們已經知道接口類型的變量底層是做爲兩個成員來實現,一個是type,一個是data。type用於存儲變量的動態類型,data用於存儲變量的具體數據。在上面的例子中,第一條打印語句輸出的是:int64。這是由於已經顯示的將類型爲int64的數據58賦值給了interface類型的變量val,因此val的底層結構應該是:(int64, 58)。咱們暫且用這種二元組的方式來描述,二元組的第一個成員爲type,第二個成員爲data。第二條打印語句輸出的是:int。這是由於字面量的整數在golang中默認的類型是int,因此這個時候val的底層結構就變成了:(int, 50)。藉助於gdb很容易觀察到這點:this

$ cd $GOPATH/src/interface_test
$ go build -gcflags "-N -l"
$ gdb interface_test

接下來講說interface類型的值和nil的比較問題。這是個比較經典的問題,也算是golang的一個坑。
spa

                                                                                                                ---來自柴大的翻譯

接着來看代碼:

package main

import (
	"fmt"
)

func main() {
	var val interface{} = nil
	if val == nil {
		fmt.Println("val is nil")
	} else {
		fmt.Println("val is not nil")
	}
}

變量val是interface類型,它的底層結構必然是(type, data)。因爲nil是untyped(無類型),而又將nil賦值給了變量val,因此val實際上存儲的是(nil, nil)。所以很容易就知道val和nil的相等比較是爲true的。

$ cd $GOPATH/src/interface_test
$ go build
$ ./interface_test
val is nil

對於將任何其它有意義的值類型賦值給val,都致使val持有一個有效的類型和數據。也就是說變量val的底層結構確定不爲(nil, nil),所以它和nil的相等比較老是爲false。

上面的討論都是在圍繞值類型來進行的。在繼續討論以前,讓咱們來看一種特例:(*interface{})(nil)。將nil轉成interface類型的指針,其實獲得的結果僅僅是空接口類型指針而且它指向無效的地址。注意是空接口類型指針而不是空指針,這二者的區別蠻大的,學過C的童鞋都知道空指針是什麼概念。

關於(*interface{})(nil)還有一些要注意的地方。這裏僅僅是拿(*interface{})(nil)來舉例,對於(*int)(nil)、(*byte)(nil)等等來講是同樣的。上面的代碼定義了接口指針類型變量val,它指向無效的地址(0x0),所以val持有無效的數據。但它是有類型的(*interface{})。因此val的底層結構應該是:(*interface{}, nil)。有時候您會看到(*interface{})(nil)的應用,好比var ptrIface = (*interface{})(nil),若是您接下來將ptrIface指向其它類型的指針,將通不過編譯。或者您這樣賦值:*ptrIface = 123,那樣的話編譯是經過了,但在運行時仍是會panic的,這是由於ptrIface指向的是無效的內存地址。其實聲明相似ptrIface這樣的變量,是由於使用者只是關心指針的類型,而忽略它存儲的值是什麼。仍是以例子來講明:

package main

import (
	"fmt"
)

func main() {
	var val interface{} = (*interface{})(nil)
	// val = (*int)(nil)
	if val == nil {
		fmt.Println("val is nil")
	} else {
		fmt.Println("val is not nil")
	}
}

很顯然,不管該指針的值是什麼:(*interface{}, nil),這樣的接口值老是非nil的,即便在該指針的內部爲nil。

$ cd $GOPATH/src/interface_test
$ go build
$ ./interface_test
val is not nil

 interface類型的變量和nil的相等比較出現最多的地方應該是error接口類型的值與nil的比較。有時候您想自定義一個返回錯誤的函數來作這個事,可能會寫出如下代碼:

package main

import (
	"fmt"
)

type data struct{}

func (this *data) Error() string { return "" }

func test() error {
	var p *data = nil
	return p
}

func main() {
	var e error = test()
	if e == nil {
		fmt.Println("e is nil")
	} else {
		fmt.Println("e is not nil")
	}
}

可是很惋惜,以上代碼是有問題的。

$ cd $GOPATH/src/interface_test
$ go build
$ ./interface_test
e is not nil

咱們能夠來分析一下。error是一個接口類型,test方法中返回的指針p雖然數據是nil,可是因爲它被返回成包裝的error類型,也即它是有類型的。因此它的底層結構應該是(*data, nil),很明顯它是非nil的。

能夠打印觀察下底層結構數據:

package main

import (
	"fmt"
	"unsafe"
)

type data struct{}

func (this *data) Error() string { return "" }

func test() error {
	var p *data = nil
	return p
}

func main() {
	var e error = test()

	d := (*struct {
		itab uintptr
		data uintptr
	})(unsafe.Pointer(&e))

	fmt.Println(d)
}

$ cd $GOPATH/src/interface_test
$ go build
$ ./interface_test
&{3078907912 0}

正確的作法應該是:

package main

import (
	"fmt"
)

type data struct{}

func (this *data) Error() string { return "" }

func bad() bool {
	return true
}

func test() error {
	var p *data = nil
	if bad() {
		return p
	}
	return nil
}

func main() {
	var e error = test()
	if e == nil {
		fmt.Println("e is nil")
	} else {
		fmt.Println("e is not nil")
	}
}
相關文章
相關標籤/搜索