• Golang Interface 内部实现


    nil

    不同的nil

    nil其实甚至不是golang的关键词,只是一个变量名。定义在 buildin/buildin.go 中

    // 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
    
    // Type is here for the purposes of documentation only. It is a stand-in
    // for any Go type, but represents the same type for any given function
    // invocation.
    type Type int
    

    换句话说,我们也可以自己声明一个nil,就会把预定义的nil覆盖了。自己试试就好了, 这肯定是不推荐的。根据这里的定义,也可以看出,在golang中nil代表了pointerchannelfuncinterfacemap 或者 slice 的zero value.

    而这里就出现一个问题,这些类型之间千差万别,一个nil怎么可以代表这么多类型呢?其实一个nil确实有些表达不了,这也是很多误会产生的原因。简单来说,nil也是有类型的,(*int)(nil)(interface{})(nil)就是两个不同的变量,它们也不相等。更有些类型比如([]int)(nil)因为是slice类型的缘故,根本就是不能比较的。

    回到上文。当interface与一个值做比较的时候,会同时比较type和value,都相等的时候,才会认为相等。上文中的(*B)(nil)的type与(A)(a)相同,都是*B,值也相同,都是nil,所以他们就相等了。

    特别的nil

    尝试一下一行很简单的代码

    var a = nil


     1 package main
     2 
     3 
     4 
     5 type StructB struct{}
     6 type StructA interface {}
     7 func main() {
     8     var e1 StructA  =   nil;
     9     var e2 * StructA  =   nil;
    10     var e3 =   nil;
    11     var e4 =   nil;
    12 }
    go build -o hello hello.go
    # command-line-arguments
    ./hello.go:10:9: use of untyped nil
    ./hello.go:11:9: use of untyped nil

    报错"use of untyped nil in variable declaration". 这很好理解,任何变量都应该有类型的,但是a的类型是什么呢,编译器百思不得其解,于是它生气了。哄一下应该没用,试着这样改一下就没问题了。

    var a = (*int)(nil)
    

    不过上文的错误信息中出现了一个特殊的nil,"untyped nil". 当我们直接写一个nil出来的时候,它是没有类型的。没有类型的nil虽然不能直接赋值给变量,但是可以与一些特定类型的变量进行比较,比如上面出现过的

    var a *B
    print(a == nil)
    

    这是合法的。这是untyped nil的特别之处,当它被拿来与一个变量进行比较的时候,根据不同的变量,就会有不同的逻辑。

    pointer

    nil pointer就是一个没有指向任何值的指针,它的值是 0x0. 做个小实验

    var a = (*int)(unsafe.Pointer(uintptr(0x0)))
    print(a == nil)  //true

    slice

    一个slice由3部分组成,pointer,len和cap. 这句话其实展开来说很长。如果能看懂下面的代码,那就是大约理解了。

    当pointer是nil,len和cap都是0的时候,这个slice等于nil. 下面做个实验

    var a = []int{}
    print(a==nil) //false 
    type aa struct {
        ptr unsafe.Pointer
        len int
        cap int
    }
    aaa := (*aa)(unsafe.Pointer(&a))
    aaa.ptr = nil
    print(a==nil) //true
    

    略微有点黑科技。简单来说,我们原本声明了一个empty slice, empty slice是不等于nil。但是我们把这个slice结构体中的ptr改成了nil,于是这个slice就变成了nil slice.

    话说,关于empty slice和nil slice取舍,golang的官方是推荐大多数情况下都应该用的nil slice的,除了是encoding JSON object等特殊情况。有点跑题,就不展开说了,具体可以参考这里的官方文档

    chanel & map & func

    这3位大哥每个都够讲一年的。但是简单来说,它们都是一个指针指向一堆implementation. 所以就可以把它们看成指针了,这个指针是nil,那就是nil了。

    ==============================================================================================================================

    最近遇到一个由于 Golang Interface 底层实现,引发的线上 panic 问题,虽然根源在于赋值操作没有保护起来,却意外地发现了关于 interface 的一些有意思的底层细节。

    假设我们现在有以下定义:

    type Interface interface {
        Run()
    }
    
    type Implement struct {
        n int
    }
    
    func (i *Implement) Run() {
        fmt.Printf(i.n)
    }
    

    对于使用者而言,一个变量无论是 Interface 类型或是 *Implement 类型,差别都不大。

    func main() {
        var i Interface
        fmt.Printf("%T
    ", i) //<nil>
        i = &Implement{n: 1}
        fmt.Printf("%T
    ", i) //*main.Implement
    
        var p *Implement
        fmt.Printf("%T
    ", p) //*main.Implement
        p = &Implement{n: 1}
        fmt.Printf("%T
    ", p) //*main.Implement
    }
    

    如果现在有这么一段代码:

    func check(i Interface) {
        if i == nil {
            return
        }
        impl := i.(*Implement)
        fmt.Println(impl.n) //Invalid memory address or nil pointer dereference
    }
    

    这段代码从逻辑上来说impl.n 永远都不会报空指针异常,因为 i 如果为空就会提前返回了。而且就算 i 为 nil,在 impl := i.(*Implement) 类型转换的时候就会直接 panic,而不是在下一行。但在线上环境上却的确在 impl.n 位置报了错误。

    在探究了 interface 底层实现后发现,在上面的 main 函数的例子里,i 和 p 虽然在使用方式上是一致的,但在内部存储的结构体却是不同的。*Implement 类型内部存储的是一个指针,对他赋值也只是赋予一个指针。而 Interface 接口底层结构却是一个类型为 iface 的 struct :

    type iface struct {
        tab  *itab
        data unsafe.Pointer
    }
    
    type itab struct {
        inter *interfacetype
        _type *_type
        hash  uint32 // copy of _type.hash. Used for type switches.
        _     [4]byte
        fun   [1]uintptr // variable sized. fun[0]==0 means _type does not implement inter.
    }
    

    当对一个接口赋值时,即对该 struct 的 tab 与 data 字段分别赋值。而该操作并非是原子性的,有可能赋值到一半,也就是 .tab 有值而 .data 为空时,就被另一个 goroutine 抢走,并进行 != nil 的判断。而 golang 却只有在 iface 两个属性同时为 nil 时候才认为是 nil,所以 check 函数内的 if 条件判断失效

    同时由于 .tab 内已经有了类型信息,所以在 impl := i.(*Implement) 类型转换的时候也能够成功转换,并不会报空指针错误,即便该 interface 的 .data 字段是 nil 。只有当实际去调用 impl.n 的时候,才会发现 .data 为 nil,从而 panic。

    interface nil 判断

    这个已经说过,当一个interface的type和value都是nil的时候,这个interface才等于nil. 这真的是个坑人无数的golang陷阱

    type A interface{}
    type B struct{}
    var a A = (*B)(nil)
    print(a == nil) //false
    a = nil
    print(a == nil) //true
    package main
    
    
    import (
        "fmt"
        "reflect"
    )
    
    
    type StructB struct{}
    func main() {
        var e  =  (*StructB)( nil);
        fmt.Println(reflect.TypeOf(e).Kind())
        fmt.Println(reflect.ValueOf(e))
        fmt.Println(reflect.TypeOf((interface{})(e)).Kind())
        fmt.Println(reflect.ValueOf((interface{})(e)))
    }
    ./hello 
    ptr
    <nil>
    ptr
    package main
    
    
    import (
        "fmt"
        "reflect"
    )
    
    
    type StructB struct{}
    type StructA interface {}
    func main() {
       // var e StructA  =  (*StructB)( nil);
        var e   =  (*StructB)( nil);
        fmt.Println(reflect.TypeOf(e).Kind())
        fmt.Println(reflect.ValueOf(e))
        fmt.Println(reflect.TypeOf((interface{})(e)).Kind())
        fmt.Println(reflect.ValueOf((interface{})(e)))
        fmt.Println(e == nil)
        e = nil
        fmt.Println(reflect.TypeOf(e).Kind())
        fmt.Println(reflect.ValueOf(e))
        fmt.Println(reflect.TypeOf((interface{})(e)).Kind())
        fmt.Println(reflect.ValueOf((interface{})(e)))
        fmt.Println(e == nil)
    }
    ptr
    <nil>
    ptr
    <nil>
    true
    ptr
    <nil>
    ptr
    <nil>
    true
    package main
    
    import (
        "fmt"
        "reflect"
    )
    
    
    type StructB struct{}
    type StructA interface {}
    func main() {
        var e StructA  =  (*StructB)( nil);
        fmt.Println(reflect.TypeOf(e).Kind())
        fmt.Println(reflect.ValueOf(e))
        fmt.Println(reflect.TypeOf((interface{})(e)).Kind())
        fmt.Println(reflect.ValueOf((interface{})(e)))
        fmt.Println(e == nil)
        e = nil
        fmt.Println(reflect.TypeOf(e).Kind())
        fmt.Println(reflect.ValueOf(e))
        fmt.Println(reflect.TypeOf((interface{})(e)).Kind())
        fmt.Println(reflect.ValueOf((interface{})(e)))
        fmt.Println(e == nil)
    }
    ptr
    <nil>
    ptr
    <nil>
    false
    panic: runtime error: invalid memory address or nil pointer dereference
    [signal SIGSEGV: segmentation violation code=0x1 addr=0x98 pc=0x9ac88]
    
    goroutine 1 [running]:
    main.main()
            /root/go_learn/example.com/hello/hello.go:19 +0x358
    package main
    
    import (
        "fmt"
        "reflect"
    )
    
    
    type StructB struct{}
    type StructA interface {}
    func main() {
        var e StructA  =  (*StructB)( nil);
        fmt.Println(reflect.TypeOf(e).Kind())
        fmt.Println(reflect.ValueOf(e))
        fmt.Println(reflect.TypeOf((interface{})(e)).Kind())
        fmt.Println(reflect.ValueOf((interface{})(e)))
        fmt.Println(e == nil)
        e = nil
        //fmt.Println(reflect.TypeOf(e).Kind())
        //fmt.Println(reflect.ValueOf(e))
        //fmt.Println(reflect.TypeOf((interface{})(e)).Kind())
        //fmt.Println(reflect.ValueOf((interface{})(e)))
        fmt.Println(e == nil)
    }
    ptr
    <nil>
    ptr
    <nil>
    false
    true
    type A interface{}
    type B struct{}
    var a *B
    
    print(a == nil)            //true
    print(a == (*B)(nil))      //true
    print((A)(a) == (*B)(nil)) //true
    
    print((A)(a) == nil)       //false
    /反射 是如何处理 匿名字段的?
    package main
    
    
    import (
        "fmt"
        "reflect"
    )
    
    type AInt int
    type BInt AInt
    
    func main() {
        var e interface{} = BInt(10)
        fmt.Println(reflect.TypeOf(e).Kind())
    }
    ./hello 
    int
    package main
    
    
    import (
        "fmt"
        "reflect"
    )
    
    
    type StructB struct{}
    func main() {
        var e *StructB
        fmt.Println(reflect.TypeOf(e).Kind())
        fmt.Println(reflect.ValueOf(e))
        fmt.Println(reflect.TypeOf((interface{})(e)).Kind())
        fmt.Println(reflect.ValueOf((interface{})(e)))
    }
    ./hello 
    ptr
    <nil>
    ptr
    <nil>

    interface空指针不为nil

    当把一个空指针对象赋值给一个interface后,再判断!= nil就不再成立了
    代码如下

    package main
    
    import "fmt"
    
    type Person interface {
    	Name() string
    }
    
    type ChenQiongHe struct {
    }
    
    func (t *ChenQiongHe) Name() string {
    	return "雪山飞猪"
    }
    
    func main() {
    	var test *ChenQiongHe
    	if test == nil {
    		fmt.Println("test == nil")
    	} else {
    		fmt.Println("test != nil")
    	}
    	//将空指针赋值给接口
    	var person Person = test
    	if person == nil {
    		fmt.Print("person == nil")
    	} else {
    		fmt.Print("person != nil")
    	}
    }
    

    运行结果

    test == nil
    person != nil
    

    test本来是nil,赋值给person后居然不能再用nil判断了

    解决方法

    使用reflect包的IsNil判断,封装为一个能用方法

    func IsNil(i interface{}) bool {
    	vi := reflect.ValueOf(i)
    	if vi.Kind() == reflect.Ptr {
    		return vi.IsNil()
    	}
    	return false
    }
    

    全部示例代码

    package main
    
    import (
    	"fmt"
    	"reflect"
    )
    
    type Person interface {
    	Name() string
    }
    
    type ChenQiongHe struct {
    }
    
    func (t *ChenQiongHe) Name() string {
    	return "雪山飞猪"
    }
    
    func main() {
    	var test *ChenQiongHe
    	if test == nil {
    		fmt.Println("test == nil")
    	} else {
    		fmt.Println("test != nil")
    	}
    	//将空指针赋值给接口
    	var person Person = test
    	if IsNil(person) {
    		fmt.Print("person == nil")
    	} else {
    		fmt.Print("person != nil")
    	}
    }
    
    func IsNil(i interface{}) bool {
    	vi := reflect.ValueOf(i)
    	if vi.Kind() == reflect.Ptr {
    		return vi.IsNil()
    	}
    	return false
    }
    
    

    运行结果

    test == nil
    person == nil


    Golang中的nil,没有人比我更懂nil!

     
  • 相关阅读:
    微信小程序
    微信小程序
    微信小程序
    微信小程序
    es5
    es5
    es5||es6
    es5
    5 个常用的软件质量指标
    Solr
  • 原文地址:https://www.cnblogs.com/dream397/p/15039952.html
Copyright © 2020-2023  润新知