goalng nil interface浅析

2022-10-27,,,

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

goalng nil interface浅析的相关教程结束。

《goalng nil interface浅析.doc》

下载本文的Word格式文档,以方便收藏与打印。