go nil介绍

go nil介绍

先从下面的一段代码开始

下面的代码是模拟了启动多线程去查数据库,之后汇总的操作

数据库:mysql

orm框架:ent

代码:

package main

import (
	"awesomeProject/ent"
	"awesomeProject/ent/user"
	"context"
	"fmt"
	"github.com/go-sql-driver/mysql"
	"log"
	"sync"
)

func main() {
	config := mysql.Config{
		User:                 "root",
		Passwd:               "toor333666",
		Net:                  "tcp",
		Addr:                 "localhost:3306",
		DBName:               "test",
		AllowNativePasswords: true,
	}

	client, err := ent.Open("mysql", config.FormatDSN())
	if err != nil {
		log.Fatalf("failed opening connection to sqlite: %v", err)
	}
	defer client.Close()
	// 创建表
	if err := client.Schema.Create(context.Background()); err != nil {
		log.Fatalf("failed creating schema resources: %v", err)
	}
	// 初始化数据
	ctx := context.Background()
	for i := 0; i < 10; i++ {
		for j := 0; j < 3; j++ {
			client.User.Create().SetName(fmt.Sprintf("name-%d",j)).SetAge(j).SaveX(ctx)
		}
	}
	// 查找数据,比如这里相差各个年龄段的数据,之后做汇总,
	searchAges := []int{1,2,122}
	users := make([]interface{}, len(searchAges))
  
	var waitGroup sync.WaitGroup // 这就是java中的countdownlatch
	waitGroup.Add(len(searchAges))

	for idx, age := range searchAges {
		go func(idx,age int) {
			var ts []*ent.User
			err := client.User.Query().Where(user.AgeEQ(age)).Select(
				user.FieldID,user.FieldName,user.FieldAge).Scan(ctx,&ts)
			if err != nil{ 
				users[idx] = err
			}else{
				users[idx] = ts
			}
			waitGroup.Done()
		}(idx,age)
	}
	waitGroup.Wait()

	res := make([]*ent.User, 0)
	for _, item := range users {
		if err,ok:= item.(error);ok{
			panic(err)
		}
		if users,ok := item.([]*ent.User);ok{
			res = append(res, users...)
		}
	}
	println(fmt.Sprintf("%v", res))
	
	client.User.Delete().ExecX(ctx)
}

go.mod如下:

module awesomeProject

go 1.18

require (
	entgo.io/ent v0.11.4
	github.com/go-sql-driver/mysql v1.6.0
)

require (
	ariga.io/atlas v0.7.3-0.20221011160332-3ca609863edd // indirect
	github.com/agext/levenshtein v1.2.1 // indirect
	github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect
	github.com/go-openapi/inflect v0.19.0 // indirect
	github.com/google/go-cmp v0.5.6 // indirect
	github.com/google/uuid v1.3.0 // indirect
	github.com/hashicorp/hcl/v2 v2.13.0 // indirect
	github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 // indirect
	github.com/zclconf/go-cty v1.8.0 // indirect
	golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
	golang.org/x/text v0.3.7 // indirect
)

代码很简单,有疑问的地方如下如所示:

在这里插入图片描述
上面的代码中searchAges参数122是没有数据的,所以结果中必然有一个是没有数据的,但item为nil,断言居然成功了。

那么,接下来就说说go中nil。

从感觉上来看,会将它和Java中的null联系起来,但go的nil和其他语言中(Java,Python)的null可不是一回事。

零值

go中,变量声明就有默认值,这个值叫做0值,

  • 0 for numeric types,
  • false for the boolean type, and
  • "" (the empty string) for strings.
  • nil for interfaces, slices, channels, maps, pointers and functions.

nil没有默认的类型

go中其他的标志符基本都有一个默认的类型比如,

比如false,true它们的类型是bool类型,iota的类型是int。

nil没有默认的类型,编译器会从上下文的信息中来推断他的类型,并且在之前

代码:

// 下面的代码会编译成功,编辑器可以从上下文中推断出nil的类型
	_ = (*struct{})(nil) // 这里代码的意思是类型转化,这里能转化不报错,说明类型是正确的。也就是说nil的类型和强转的类型是一样的
	_ = []int(nil)
	_ = map[int]bool(nil)
	_ = chan string(nil)
	_ = (func())(nil)
	_ = interface{}(nil)

 // 下面的和上面的等价的
	var _ *struct{} = nil
	var _ []int = nil
	var _ map[int]bool = nil
	var _ chan string = nil
	var _ func() = nil
	var _ interface{} = nil

 // 编译会失败,信息不足够,编译器也不知道是什么类型。
	var _ = nil

nil在go中不是一个关键词

nil值在一个上下文中可以被替换掉,代码如下

	nil := 123
	fmt.Println(nil) // 123

	// 下面的编译会失败,在当前的上下文中
  // nil已经是一个int的值了,代表的是int类型
	var _ map[string]int = nil

不同类型的空值大小可能不同

同一类型的所有值的内存布局总是相同的。此类型下的 nil和非nil的值大小相等,但是不同类型的nil可能不一样。

代码:

package main

import (
	"fmt"
	"unsafe"
)

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
}

上面运行的结果不是不变的,编译器和架构会影响。以上打印的结果适用于64位体系结构和标准 Go 编译器。对于32位体系结构,打印大小将减半。

两个不同类型的nil可能无法比较

例如,下面示例中的两个比较都无法编译。原因是,在每次比较中,两个操作数都不能隐式转换为另一个操作数的类型。

go的比较规则可以看这个官网文档

简而言之。如果其中一个值可以隐式转换为另一个值的类型,则两个值是可比的。

代码:

// 编译会失败,类型不匹配
var _ = (*int)(nil) == (*bool)(nil)         // error
var _ = (chan int)(nil) == (chan bool)(nil) // error

下面的代码可以编译

type IntPtr *int
// 他俩底层的类型都是IntPtr*
var _ = IntPtr(nil) == (*int)(nil)

// go中每一个interface{}可以表示所有
var _ = (interface{})(nil) == (*int)(nil)

// 当向通道可以转换为单向通道,并且他俩底层的数据类型一致
var _ = (chan int)(nil) == (chan<- int)(nil)
var _ = (chan int)(nil) == (<-chan int)(nil)

两个相同类型的 Nil 值可能无法比较

在 Go 中,映射、切片和函数类型不支持比较。比较无法比较的类型的两个值(包括空值)是非法的。 这是语法限制,其实和nil值感觉不是很大。

下列比较无法编译。

var _ = ([]int)(nil) == ([]int)(nil)
var _ = (map[string]int)(nil) == (map[string]int)(nil)
var _ = (func())(nil) == (func())(nil)

下面是ok的

	var _ = ([]int)(nil) == nil
	var _ = (map[string]int)(nil) == nil
	var _ = (func())(nil) == nil

两个nil值可能不相等

如果比较的nil中有一个是接口值,结果总等于false,因为他们的底层的类型不一致,编译器不能推断出来,我们知道go的接口的底层有一对数据(类型和实际的值。)对于下面的例子中

interface{}(nil) 的一对数据为(type=nil,value=nil)

(*int)(nil) )的一对数据为 (type=*int,value=nil)

fmt.Println( (interface{})(nil) == (*int)(nil) ) // false

从nil值中检索数据不会Panic

从空映射值中检索元素将始终返回零元素值。

fmt.Println( (map[string]int)(nil)["key"] ) // 0
fmt.Println( (map[int]bool)(nil)[123] )     // false
fmt.Println( (map[int]*int64)(nil)[123] )   // 

从nil的Channels、Maps、Slices、数组指针遍历是合法的

遍历map和slices类型的nil的步长为0

遍历数组指针的步长是它对应的数组的长度

遍历一个nil的channel会堵塞当前线程

代码:

for range []int(nil) {
	fmt.Println("Hello")
} // 不会输出


for range map[string]string(nil) {
	fmt.Println("world")
} // 不会出输出

for i := range (*[5]int)(nil) {
	fmt.Println(i)
} // 输出0,1,2,3,4


for range chan bool(nil) { // 堵塞在这里,被死锁检测到。
	fmt.Println("Bye")
}

通过非接口的nil值来调用方法不会panic

代码:

package main

type Slice []bool

func (s Slice) Length() int {
	return len(s)
}

func (s Slice) Modify(i int, x bool) {
	s[i] = x // panic if s is nil
}

func (p *Slice) DoNothing() {
}

func (p *Slice) Append(x bool) {
	*p = append(*p, x) // panic if p is nil
}

func main() {
 // 下面的不会抛错
	_ = ((Slice)(nil)).Length
	_ = ((Slice)(nil)).Modify
	_ = ((*Slice)(nil)).DoNothing
	_ = ((*Slice)(nil)).Append


	_ = ((Slice)(nil)).Length()
	((*Slice)(nil)).DoNothing()

	// 下面的会报错,但是报错不是在调用方法的时候,而是在方法里面操作对象的时候报错
	/*
	((Slice)(nil)).Modify(0, true)
	((*Slice)(nil)).Append(true)
	*/
}

*new(T)的结果就是T类型下面的0值

package main

import "fmt"

func main() {
	fmt.Println(*new(*int) == nil)         // true
	fmt.Println(*new([]int) == nil)        // true
	fmt.Println(*new(map[int]bool) == nil) // true
	fmt.Println(*new(chan string) == nil)  // true
	fmt.Println(*new(func()) == nil)       // true
	fmt.Println(*new(interface{}) == nil)  // true
}

总结

在 Go 中,为了简单和方便,nil 被设计为一个标识符,可以用来表示某些类型的零值。他不是一个不变的值,而是随着不同类型有不同的类型,在日常的编码中,编译器会根据上下文来推导出。

回答代码中的问题

首先代码中出现问题的是interface{},按照上面说的「两个nil值可能不相等」来说,结果已经推出来了。在go中接口是一堆方法的集合,并且接口变量的底层是一对数据结构(类型,实际的值),断言,反射是来拿到它们在做处理,先看下面的代码

var p *int              // (type=*int,value=nil)
var i interface{}       // (type=nil,value=nil)

if i != p {             // (type=*int,value=nil) != (type=nil,value=nil)
// to successfully compare these values, both type and value must match
    fmt.Println("not a nil")
}
// 运行结果是   "not a nil"

这两虽然都是nil,但他们的类型不一致。

在看一段代码

var p *int              // (type=*int,value=nil)
	var i interface{}       // (type=nil,value=nil)

	if p != nil{ // (type=*int,value=nil) != (type=*int,value=nil)
		fmt.Println("not a nil p")
	}


	if i != nil {           // (type=nil,value=nil) != (type=nil,value=nil)
		fmt.Println("not a nil i1")
	}

	i = p                   // assign p to i

	// a hardcoded nil is always nil,nil (type,value)
	if i != nil {           // (type=*int,value=nil) != (type=nil,value=nil)
		fmt.Println("not a nil i2")
	}
// 运行结果是   "not a nil i"

类比一开始的代码,结合上面的分析,看下面的解释。

在这里插入图片描述### 如何处理这种情况

有三种方式,代码如下

import "reflect"
import "unsafe"

func do(v interface{}){
    // 可能有问题,因为这是接口类型的变量
    // 比如传递进来的是 (type=*int,value=nil),永远不会为true,这里的nil是 (type=nil,value=nil)
    if v == nil {

    }
		
   // 这种方式是ok的,将nil值转换为同一类型下来比较
   // 但是这样的比较要对业务很熟悉,实际动起手来比较麻烦
    if v == (*int)(nil){
      
    }
		
  // 因为是nil,所以可以拿到 反射中的Value是否为nil
    if reflect.ValueOf(v).IsNil() {
    
    }
	// 通过unsafe来直接检查一个接口类型的变量的value部分是否为0,但是如果value不是可以为nil的那5中类型的话也不会报错
    if (*[2]uintptr)(unsafe.Pointer(&v))[1] == 0 {
   
    }

}

对于上面的代码,可以采取下面的方式来判断

在这里插入图片描述

相关博客:

Why Golang Nil Is Not Always Nil? Nil Explained

nils in Go


关于博客这件事,我是把它当做我的笔记,里面有很多的内容反映了我思考的过程,因为思维有限,不免有些内容有出入,如果有问题,欢迎指出。一同探讨。谢谢。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值