goalng nil interface浅析

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。

持续学习

通过遇到的问题引申学习主要解决两个问题

  1. nil到底是什么?
    第一部分标题1-3整理了一些nil在go中的使用,对于不同类型对nil比较的实际操作并举了例子。
  2. 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

猜你喜欢

转载自www.cnblogs.com/voriya/p/golang-interface-nil.html