Golang nil使用避坑

Golang nil使用避坑

Golang 中的 nil 是一个很特殊的值,它不同于其他编程语言中的 nullNonenil。在 Golang 中,nil 可以用于表示接口、函数、通道、映射、切片等的零值。虽然 nil 在某些情况下非常有用,但如果不正确地使用它,也会导致一些很难发现的错误。

各类型变量的默认值

  • 布尔类型的零值(初始值)为 false
  • 数值类型的零值为 0
  • 字符串类型的零值为空字符串""
  • 其他的均为nil

nil的数据类型

  • 单独的一个nil值本身没有类型,只有通过上下文,判断其赋值对象,才能判断其类型
  • 不同类型的nil值无法进行比较
  • 不同类型的nil值,内存大小也不一样
  • nil其实包含两个值,分别是type和data
func main() {
   var p *struct{} = nil
   fmt.Println(unsafe.Sizeof(p)) // 8
   var s []int = nil
   fmt.Println(unsafe.Sizeof(s)) // 24
   var m map[int]bool = nil
   fmt.Println(unsafe.Sizeof(m)) // 8
   var c chan string = nil
   fmt.Println(unsafe.Sizeof(c)) // 8
   var f func() = nil
   fmt.Println(unsafe.Sizeof(f)) // 8
   var i interface{} = nil
   fmt.Println(unsafe.Sizeof(i)) // 16
}

关于nil的一些踩坑记录

nil 赋值给非接口类型

在 Golang 中,你不能将 nil 直接赋值给非接口类型,例如:

var x string = nil // 同理 int/float/bool等类型也不可以

这将导致编译错误:cannot use nil as value in assignment。要避免这个问题,可以将 nil 赋值给非接口类型之前,先将其定义出来再赋值,例如:

var x string = ""

此类情况一般IDE可以提前识别到,在无IDE情况下需要注意。

切片判空慎用nil

func F() {
    // 定义变量
    var s []string
    fmt.Printf("1:nil=%t\n", s == nil) // true
    // 组合字面量方式
    s = []string{}
    fmt.Printf("1:nil=%t\n", s == nil) // false
    // make方式
    s = make([]string, 0)
    fmt.Printf("1:nil=%t\n", s == nil) // false
}
func A()[]string{
    ...
    return nil
}

func B(){
    c := A()
    // 判断c是否是空数组
    if c == nil {} // 不推荐
    if len(c) == 0 {} // 推荐    
}

nilany的使用

nilany比较类似,本身并不代表任何类型,仅能通过上下文调用才可以判断类型

func F(){
    var x *string
    var y *string
    BothNil := func(a any, b any) bool {
       return a == nil && b == nil
    }
    BothNilInterface := func(a interface{}, b interface{}) bool {
       return a == nil && b == nil
    }
    fmt.Println(x == nil && y == nil) // true
    fmt.Println(BothNil(nil, nil)) // true
    fmt.Println(BothNil(x, y)) // false
    fmt.Println(BothNilInterface(nil, nil)) // true
    fmt.Println(BothNilInterface(x, y)) // false
}

因为any/interface{}数据,其定义中不仅包含其所代表的值,同样还有其代表值的类型。直接使用any/interface{}做nil判断,不仅需要判断date是否为nil,还需要判断其类型是否为空(类型只要被赋值interface{},一般就不会为空)

// goloang源码runtime.eface定义
type eface struct { // 16 字节
        _type *_type
        data  unsafe.Pointer
}

是否有其他方法可以规避,有,但还有别的坑

func F(){
    var x *string
    var y *string
    BothNil := func(a any, b any) bool {
       return reflect.ValueOf(a).IsNil() && reflect.ValueOf(b).IsNil()
    }
    fmt.Println(BothNil(x, y)) // true
    var xx int
    var yy int
    fmt.Println(BothNil(xx, yy)) // panic
}

// 或者用一个同类型的nil做比较
BothEq := func(a any, b any) bool {
   return a == b
}
新零售SaaS架构:订单履约系…
Comments are closed.