0.遇到一個問題代碼func GetMap (i interface{})(map[string]interface{}){ if i == nil { //false ??? i = make(map[string]interface) fmt.Println("xxxxx") }}var tes... ...
0.遇到一個問題
代碼
func GetMap (i interface{})(map[string]interface{}){
if i == nil { //false ???
i = make(map[string]interface)
fmt.Println("xxxxx")
}
}
var testMap map[string]interface{}
getMap := GetMap(testMap)
getMap["add"] = "add" //panic
問題:比較困惑的是對於一個傳進來的testMap是nil,但是在GetMap 裡面if卻是false。實踐看來函數內部形參確實已經不是nil。那麼interface{}判斷nil的具體是什麼過程?
找答案: 看了這個視頻understanding nil 整理了一下
總結答案 :
- 空interface實際數據結構是包含type和value兩個欄位。
- 判斷interface==nil的時候需要type和value都是null,才等於nil。
- testMap賦值給i之後,介面包含了賦值對象的類型細信息。的type欄位不再是null了,因此代碼if的結果就是false。
持續學習
通過遇到的問題引申學習主要解決兩個問題
- nil到底是什麼?
第一部分標題1-3整理了一些nil在go中的使用,對於不同類型對nil比較的實際操作並舉了例子。 - interface的存儲了什麼?
第二部分標題4對interface實際記憶體中結構進行了探索。包括帶方法的interface和空interface
1.nil是什麼
個人理解: 學習之前類比為c的null空指針。學習之後知道有點以偏概全
官方定義:
// nil is a predeclared identifier representing the zero value for a
// pointer, channel, func, interface, map, or slice type.
var nil Type
// Type must be a pointer, channel, func, interface, map, or slice type
- nil 並不是關鍵字,只是預定義的標識符。
- nil代表數據類型的0值,並不僅僅是空指針。
- nil 可以和 pointer, channel, func, interface, map, or slice 進行比較
不同類型對應的0值如下:
類型 | 零值 |
---|---|
numbers | 0 |
string | "" |
bool | false |
pointer | nil |
slices | nil |
maps | nil |
channels | nil |
functions | nil |
interfaces | nil |
結構體的0值
對於結構體來說,每個欄位都是nil值,整個結構體才是nil
type Person struct {
Age int
Name string
Friends []Person
}
var p Person // Person{0, "", nil}
2.nil類型
nil 沒有類型 不是關鍵字 可被修改
var nil = errors.New("***")
不同類型對應nil實際判斷標準
類型 | 實際存儲 | nil判斷 |
---|---|---|
pointers | 類c | 不指向任何記憶體, 記憶體安全 垃圾回收 |
slices | [ptr(*elem)\ | len()|cap()] |
maps,channels,functions | ptr | 沒有初始化 |
interface | (type,data) | (nil,nil) |
- 特別的 -- interface 的 nil
interface 包含type 和 data
nil interface指的是 type是nil,且 value 是nil
var s fmt.Stringer // Stringer(nil,nil)
fmt.Println(s == nil) //true
//(nil, nil) == nil
var p *Person //nil of type *Person
var s fmt.Stringer = p //Stringer(*Person nil)
fmt.Println(s == nil) // false
//(*Person, nil) != nil
- nil interface 不等於 nil??
錯誤例子:這裡和最開始的問題類似,函數返回error的時候,定義了一個空的error並返回,在函數碗面判斷error!=nil的時候並不是true。所以在實際開發的時候,對於沒有error的情況,要直接返回nil。
type error interface {
Error() string
}
func do() error { // 實際 error(*doError, nil)
var err *doError
return err //類型是 *doError 的nil
}
func main(){
err := do() //error (*doError , nil)
fmt.Println(err == nil) //false
}
正確方式:
不定義error類型,直接返回nil
func do() *doError { //nil of type *doError
return nil
}
func main(){
err := do() //nil of type *doError
fmt.Println(err == nil) //true
}
對於多層函數調用
裡層函數定義了返回值雖然是nil,但是包含了type類型。所以避免這種寫法
func do() *doError{ //nil of type *doError
return nil
}
func wrapDo error { //error(*doError , nil)
return do() //nil of type *doError
}
func main(){
err := wrapDo() //error(*doError,nil)
fmt.Println(err == nil)//false
}
綜上:不要聲明具體error類型,以為的nil interface實際上已經不是nil了。以上和文章問題的初衷是一致的。因為某種類型的nil賦值給nil interface之後 interface!=nil了。 賦值後的interface雖然沒有值,但是已經有類型信息了
nil 不僅僅是 null
3.不同type nil的操作
pointers 取值panic
var p *int
p == nil //true
*p //panic
nil receiver
slices 可遍歷 可append 不可取值panic
var s []slice
len(s) // 0
cap(s) // 0
for range s // zero times
s[i] // panic:index out of range
append // ok 自動擴容 1024以內2倍擴容 以上1.25倍
maps 可以遍歷 可取值 可賦值
var m map[t]u
len(m) //0
for range m { // zero times
v,ok := m[i] // zero(u), false
m[i] = x
}
channels 讀寫阻塞 close會panic
//nil channel
var c chan t
<-c //blocks forever
c<-x // blocks forever
close(c) // panic: close of nil channel
//closed channel
v, ok <- c //zero(t),false
c <- x //panic: send on closed channel
close(c) //panic: close of nil channel
interfaces
type Summer interface{
func Sum() int
}
//pointer
var t *tree
var s Summer = t
fmt.Println(t == nil, s.Sum() ) //true, 0
//slice
type ints []int
func (i ints)Sum() int{
s:=0
for _, v := range i {
s += v
}
return s
}
var i ints
var s Summer = i
fmt.Println( i == nil, s.Sum()) //true , 0
// nil value can satisfy nil interface
4.interface
gopher 講的 interface使用Tomas Senart - Embrace the Interface
Google Understanding Go Interfaces
- writing generic algorithm
- hiding implementation detail
- providing interception points
用於聲明方法集合,可嵌套,不包含方法實現。不定義欄位。
優勢:隱藏一些具體的實現,泛型編程,不用聲明實現哪些func運行時確定
4.1 interface數據結構
有方法的介面
iface
iface 是 runtime 中對 interface 進行表示的根類型 (src/runtime/runtime2.go)
type iface struct{
tab *itab //類型信息
data unsafe.Pointer //實際對象指針
}
type itab struct {
inter *interfacetype //介面類型
_type *type //實際對象類型
fun [1]uintptr //實際對象方法地址
...
}
iface 的 itab欄位存儲介面定義的方法相關信息,method 的具體實現存放在 itab.fun變數里。描述interface類型和其指向的數據類型的數據結構可以看下麵gdb調試過程結構的列印.
data存儲interface持有的具體的值信息,不可被修改。當賦值的a被修改並不會影響interface裡面的data
itab
_type 這個類型是 runtime 對任意 Go 語言類型的內部表示。_type 類型描述了一個“類型”的每一個方面: 類型名字,特性(e.g. 大小,對齊方式...),某種程度上類型的行為(e.g. 比較,哈希...) 也包含在內了。。(src/runtime/type.go)
interfacetype 的指針,這隻是一個包裝了 _type 和額外的與 interface 相關的信息的欄位。描述了interface本身的類型。(src/runtime/type.go)
func 數組持有組成該interface虛表的的函數的指針。
空介面
- 空介面可以被任何類型賦值,預設值是nil。
- 沒有方法
- 存儲結構也和有方法的interface不同。如下
eface
(src/runtime/runtime2.go)
type eface struct {
_type *_type //對象類型信息
data unsafe.Pointer //對象指針
}
type _type struct {
size uintptr // type size
ptrdata uintptr // size of memory prefix holding all pointers
hash uint32 // hash of type; avoids computation in hash tables
tflag tflag // extra type information flags
align uint8 // alignment of variable with this type
fieldalign uint8 // alignment of struct field with this type
kind uint8 // enumeration for C
alg *typeAlg // algorithm table
gcdata *byte // garbage collection data
str nameOff // string form
ptrToThis typeOff // type for pointer to this type, may be zero
}
- eface沒有方法聲明,存儲*_type包含類型信息。可以看到一個空介面也存了兩個欄位,這裡根本解釋了最開始提到的問題,對於判斷interface{}==nil的時候,需要保證介面兩個欄位都是null才是true。下麵4.2debug的例子中調試29行,34行和35行對比ei的時候可以看到,雖然一個nil struct賦值給了interface{}後,空介面的_type,data欄位都已經不是null了。
- interface被賦值之後也支持比較。(如果賦值對象支持比較)
func main(){
var t1,t2 interface{}
fmt.Println(t1==nil) //true
fmt.Println(t1==t2) //true
t1,t2=100,100
fmt.Println(t1==t2) // true
t1,t2=map[string]int{},map[string]int{}
fmt.Println(t1==t2)} //panic runtime error:comparing uncomparable type map[string]int
}
斷言 .(asert)
interface{}可作為函數參數,實現泛型編程。
asert 用於判斷變數類型 並且 可以判斷變數是否實現了某個介面
type data int
func(d data)String()string{
return fmt.Sprintf("data:%d",d)
}
func main(){
var d data=15
var x interface{}=d
if n,ok:=x.(fmt.Stringer);ok{ //轉換為更具體的介面類型
fmt.Println(n)
}
if d2,ok:=x.(data);ok{ //轉換回原始類型
fmt.Println(d2)
}
e:=x.(error) //錯誤:main.data is not error
fmt.Println(e)
}
- 使用ok-idiom模式不用擔心轉換失敗時候panic
- 利用switch可以在多種類型條件下進行匹配 ps type switch不支持fallthrought
func main(){
var x interface{}=func(x int)string{return fmt.Sprintf("d:%d",x)}
switchv:=x.(type){ //局部變數v是類型轉換後的結果
case nil :
fmt.Println("nil")
case*int:
fmt.Println(*v)
case func(int)string:
fmt.Println( v(100) )
case fmt.Stringer:
fmt.Println(v)
default:
fmt.Println("unknown")
}
}
output:100
4.2 debug一下
code
1 package main
2 import(
3 "fmt"
4 )
5
6 type A struct {
7
8 }
9 type Aer interface {
10 AerGet()
11 AerSet()
12 }
13 func (a A)AerGet(){
14 fmt.Println("AerGet")
15 }
16
17 func (a *A)AerSet(){
18 fmt.Println("AerSet")
19 }
20 func main(){
21 var a A
22 var aer Aer
23 aer = &a
24 aer.AerGet()
25 aer.AerSet()
26
27 var ei interface{}
28 if ei == nil {
29 fmt.Println("ei is nil")
30 }else {
31 fmt.Println("ei not nil")
32 }
33 var aa A
34 ei = aa
35 if ei == nil {
36 fmt.Println("ei is nil")
37 }else {
38 fmt.Println("ei not nil")
39 }
40
41 }
debug
mac版本10.14.2 gdb版本8.2.1。mac系統更當前新版本之後gdb並不能使用了,嘗試創建證書授權給gdb,但是並沒有成功。教程gdb wiki
因此使用了lldb。主要列印了eface和iface記憶體,利用用nil struct對interface{}賦值之後interface{}的記憶體變化。
* thread #1, stop reason = breakpoint 1.1
frame #0: 0x000000000109376b test`main.main at interfacei.go:23
20 func main(){
21 var a A
22 var aer Aer
-> 23 aer = &a
24 aer.AerGet()
25 aer.AerSet()
26
Target 0: (test) stopped.
(lldb) p aer //interface iface struct
(main.Aer) aer = {
tab = 0x0000000000000000
data = 0x0000000000000000
}
(lldb) n
21 var a A
22 var aer Aer
23 aer = &a
-> 24 aer.AerGet()
25 aer.AerSet()
26
27 var ei interface{}
Target 0: (test) stopped.
(lldb) p aer
(main.Aer) aer = {
tab = 0x000000000112c580
data = 0x000000000115b860
}
(lldb) p &aer
(*main.Aer) = 0x000000000112c580
(lldb) p *aer.tab // itab struct
(runtime.itab) *tab = {
inter = 0x00000000010acc60
_type = 0x00000000010aba80
link = 0x0000000000000000
hash = 474031097
bad = false
inhash = true
unused = ([0] = 0, [1] = 0)
fun = ([0] = 0x0000000001093a10)
}
(lldb) p *aer.tab._type
(runtime._type) *_type = {
size = 0x0000000000000008
ptrdata = 0x0000000000000008
hash = 474031097
tflag = 1
align = 8
fieldalign = 8
kind = 54
alg = 0x000000000113bd50
gcdata = 0x00000000010d4ae4
str = 6450
ptrToThis = 0
}
(lldb) p *aer.tab.inter //inter struct
(runtime.interfacetype) *inter = {
typ = {
size = 0x0000000000000010
ptrdata = 0x0000000000000010
hash = 2400961726
tflag = 7
align = 8
fieldalign = 8
kind = 20
alg = 0x000000000113bd80
gcdata = 0x00000000010d4ae6
str = 10139
ptrToThis = 45184
}
pkgpath = {
bytes = 0x0000000001093e78
}
mhdr = (len 2, cap 2) {
[0] = (name = 5032, ityp = 61664)
[1] = (name = 5041, ityp = 61664)
}
}
(lldb) n
ei is nil
27 var ei interface{}
28 if ei == nil {
29 fmt.Println("ei is nil")
30 }else {
31 fmt.Println("ei not nil")
32 }
33 var aa A //aa == nil
-> 34 ei = aa
35 if ei == nil {
36 fmt.Println("ei is nil")
37 }else {
Target 0: (test) stopped.
(lldb) p ei //interface{} == ni
(interface {}) ei = { //eface struct
_type = 0x0000000000000000
data = 0x0000000000000000
}
(lldb) n
32 }
33 var aa A
34 ei = aa
-> 35 if ei == nil {
36 fmt.Println("ei is nil")
37 }else {
38 fmt.Println("ei not nil")
(lldb) p ei
(interface {}) ei = { //interface{} != nil
_type = 0x00000000010acbe0
data = 0x000000000115b860
}
(lldb) p *ei._type // _type struct
(runtime._type) *_type = {
size = 0x0000000000000000
ptrdata = 0x0000000000000000
hash = 875453117
tflag = 7
align = 1
fieldalign = 1
kind = 153
alg = 0x000000000113bd10
gcdata = 0x00000000010d4ae4
str = 6450
ptrToThis = 98304
}
彙編
彙編代碼看不大懂,放在這裡督促學習。
go build -gcflags '-l' -o interfacei interfacei.go
go tool objdump -s "main\.main" interfacei
TEXT main.main(SB) /Users/didi/go/src/test/interface/interfacei.go
interfacei.go:20 0x10936b0 65488b0c25a0080000 MOVQ GS:0x8a0, CX
interfacei.go:20 0x10936b9 483b6110 CMPQ 0x10(CX), SP
interfacei.go:20 0x10936bd 0f8635010000 JBE 0x10937f8
interfacei.go:20 0x10936c3 4883ec70 SUBQ $0x70, SP
interfacei.go:20 0x10936c7 48896c2468 MOVQ BP, 0x68(SP)
interfacei.go:20 0x10936cc 488d6c2468 LEAQ 0x68(SP), BP
interfacei.go:20 0x10936d1 488d05e8920100 LEAQ 0x192e8(IP), AX
interfacei.go:21 0x10936d8 48890424 MOVQ AX, 0(SP)
interfacei.go:21 0x10936dc e8afb7f7ff CALL runtime.newobject(SB)
interfacei.go:21 0x10936e1 488b442408 MOVQ 0x8(SP), AX
interfacei.go:21 0x10936e6 4889442430 MOVQ AX, 0x30(SP)
//24 aer.AerGet()
interfacei.go:24 0x10936eb 48890424 MOVQ AX, 0(SP)
interfacei.go:24 0x10936ef e87c010000 CALL main.(*A).AerGet(SB)
interfacei.go:24 0x10936f4 488b442430 MOVQ 0x30(SP), AX
//25 aer.AerSet()
interfacei.go:25 0x10936f9 48890424 MOVQ AX, 0(SP)
interfacei.go:25 0x10936fd e82effffff CALL main.(*A).AerSet(SB)
interfacei.go:29 0x1093702 48c744244800000000 MOVQ $0x0, 0x48(SP)
interfacei.go:29 0x109370b 48c744245000000000 MOVQ $0x0, 0x50(SP)
interfacei.go:29 0x1093714 488d0505030100 LEAQ 0x10305(IP), AX
interfacei.go:29 0x109371b 4889442448 MOVQ AX, 0x48(SP)
interfacei.go:29 0x1093720 488d0dd9240400 LEAQ 0x424d9(IP), CX
interfacei.go:29 0x1093727 48894c2450 MOVQ CX, 0x50(SP)
interfacei.go:29 0x109372c 488d4c2448 LEAQ 0x48(SP), CX
interfacei.go:29 0x1093731 48890c24 MOVQ CX, 0(SP)
interfacei.go:29 0x1093735 48c744240801000000 MOVQ $0x1, 0x8(SP)
interfacei.go:29 0x109373e 48c744241001000000 MOVQ $0x1, 0x10(SP)
interfacei.go:29 0x1093747 e8b48dffff CALL fmt.Println(SB)
interfacei.go:29 0x109374c 488d056d920100 LEAQ 0x1926d(IP), AX
// 35 if ei == nil {
interfacei.go:35 0x1093753 4885c0 TESTQ AX, AX
interfacei.go:35 0x1093756 7454 JE 0x10937ac
//38 fmt.Println("ei not nil")
interfacei.go:38 0x1093758 48c744245800000000 MOVQ $0x0, 0x58(SP)
interfacei.go:38 0x1093761 48c744246000000000 MOVQ $0x0, 0x60(SP)
interfacei.go:38 0x109376a 488d05af020100 LEAQ 0x102af(IP), AX
interfacei.go:38 0x1093771 4889442458 MOVQ AX, 0x58(SP)
interfacei.go:38 0x1093776 488d05a3240400 LEAQ 0x424a3(IP), AX
interfacei.go:38 0x109377d 4889442460 MOVQ AX, 0x60(SP)
interfacei.go:38 0x1093782 488d442458 LEAQ 0x58(SP), AX
interfacei.go:38 0x1093787 48890424 MOVQ AX, 0(SP)
interfacei.go:38 0x109378b 48c744240801000000 MOVQ $0x1, 0x8(SP)
interfacei.go:38 0x1093794 48c744241001000000 MOVQ $0x1, 0x10(SP)
interfacei.go:38 0x109379d e85e8dffff CALL fmt.Println(SB)
interfacei.go:41 0x10937a2 488b6c2468 MOVQ 0x68(SP), BP
interfacei.go:41 0x10937a7 4883c470 ADDQ $0x70, SP
interfacei.go:41 0x10937ab c3 RET
interfacei.go:36 0x10937ac 48c744243800000000 MOVQ $0x0, 0x38(SP)
interfacei.go:36 0x10937b5 48c744244000000000 MOVQ $0x0, 0x40(SP)
interfacei.go:36 0x10937be 488d055b020100 LEAQ 0x1025b(IP), AX
interfacei.go:36 0x10937c5 4889442438 MOVQ AX, 0x38(SP)
interfacei.go:36 0x10937ca 488d053f240400 LEAQ 0x4243f(IP), AX
interfacei.go:36 0x10937d1 4889442440 MOVQ AX, 0x40(SP)
interfacei.go:36 0x10937d6 488d442438 LEAQ 0x38(SP), AX
interfacei.go:36 0x10937db 48890424 MOVQ AX, 0(SP)
interfacei.go:36 0x10937df 48c744240801000000 MOVQ $0x1, 0x8(SP)
interfacei.go:36 0x10937e8 48c744241001000000 MOVQ $0x1, 0x10(SP)
interfacei.go:36 0x10937f1 e80a8dffff CALL fmt.Println(SB)
interfacei.go:35 0x10937f6 ebaa JMP 0x10937a2
interfacei.go:20 0x10937f8 e883aafbff CALL runtime.morestack_noctxt(SB)
interfacei.go:20 0x10937fd e9aefeffff JMP main.main(SB)
參考
Go Interface 源碼剖析
Tomas Senart - Embrace the Interface
Google Understanding Go Interfaces
understanding nil