判断 interface 是否为nil
 1func interceptor(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
 2	start := time.Now()
 3	err := invoker(ctx, method, req, reply, cc, opts...)
 4	logger.Infof("[gRPC] method=%s req=%v rep=%+v duration=%s error=%v", method, req, reply, time.Since(start), err)
 5	if err != nil {
 6		return err
 7	}
 8
 9	return nil
10}
gRPC 拦截器中想添加对服务端响应结果是否为空的判断,避免后续取字段的 panic(不想每次判断)。原想直接使用 reply == nil 来判断,但是竟然是 false ???简单搜索后得知以下信息:
- 类型信息(Type):存储接口的动态类型。
- 值信息(Value):存储接口的动态值。
当一个接口变量的动态值为 nil 时,如果它的动态类型不是 nil,那么 interface{} == nil 的判断会是 false。上文中的 reply 是有明确的实现类型的,因此 interface != nil。在实际开发中,当 interface 类型的返回值已经明确为nil时,应该直接返回 nil,而不是具体实现结构的未赋值空指针。
1import "reflect"
2
3func isNil(i interface{}) bool {
4    if i == nil {
5        return true
6    }
7    v := reflect.ValueOf(i)
8    return v.Kind() == reflect.Ptr && v.IsNil()
9}
修改为使用上述方法来判断 interface 是否为 nil,通常来说成功了,但是 isNil(reply) 仍然是 false???尝试直接打印无果,格式化后发现原本定义了两个字段的 reply,竟然包含五个字段,自定义字段确实是nil,但是还有三个我不知道的字段。
1type XxxInfo struct {
2	state         protoimpl.MessageState
3	sizeCache     protoimpl.SizeCache
4	unknownFields protoimpl.UnknownFields
5
6	Id              int32   `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"`
7	Type            int32   `protobuf:"varint,2,opt,name=type,proto3" json:"type,omitempty"`
8}
查看 .pb.go 文件可以看到完整结构,前三个为未导出的字段,打印出来均不是指针类型,因此不能直接通过isNil方法来判断。
 1func isCustomFieldNil(v interface{}) bool {
 2	if v == nil {
 3		return true
 4	}
 5	val := reflect.ValueOf(v).Elem()
 6	for i := range val.NumField() {
 7		field := val.Field(i)
 8		fieldName := val.Type().Field(i).Name
 9		logger.Infof("fieldName: %s, value: %v, type: %v", fieldName, field, field.Type())
10		// 跳过内部字段
11		if fieldName == "state" || fieldName == "sizeCache" || fieldName == "unknownFields" {
12			continue
13		}
14		// 检查指针类型字段是否为nil
15		if field.Kind() == reflect.Ptr && field.IsNil() {
16			return true
17		}
18	}
19
20	return false
21}
当前做法是跳过了这三个字段,不知道有没有更好的办法。
var、new、make 的区别
判断切片长度是否为0
最初 VSCode 对我的一行代码有个提示,代码内容大致如下
1info := new([]Info)
2if info == nil || len(*info) == 0 {
3    ...
4}
在第二个 if 语句提示内容为:
impossible condition: non-nil == nilnilnesscond
意思是说,不可能的条件,info 不可能是 nil,只需要判断 len(*info) == 0
在这段代码中,info 是通过 new([]Info) 创建的,是一个指向切片(slice)的指针,指针指向的是一个初始化的切片,切片的底层数据 nil(零值切片),即 *info 的初始值是 nil。
从这之后,我误以为判断一个切片是否为空的时候只需要判断 len(*info) == 0,但是如下代码却发生报错
panic: runtime error: invalid memory address or nil pointer dereference
1var info *[]Info
2if len(*info) == 0 {
3	return nil
4}
var info *[]Info 仅仅声明了一个指向切片的指针,但未分配内存,此时 info 的值是 nil, len(*info) 会尝试对 info 进行解引用,*info 就会引发 panic。
| 声明方式 | 内存分配 | 指针是否 nil | 底层切片状态 | len(*info)是否安全 | 
|---|---|---|---|---|
| new([]Info) | 分配内存,返回指向零值切片的指针 | 否 | 零值切片(底层数组为 nil) | 安全(返回 0) | 
| var info *[]Info | 仅声明指针,未分配内存 | 是 | 指针本身为 nil | 触发 panic | 
new 和 make 区别
- new 的行为 - 作用: - new用于为指定类型分配内存,并返回指向该类型零值的指针。
- 切片内存分配: - 当使用 - new([]T)时,会为切片类型- []T分配内存,并返回一个指向切片零值的指针。
- 切片的零值是一个零值切片,即: - len和- cap均为- 0。
- 底层数组为 nil。
 
- 示例: - 1info := new([]int) // info 是一个指针,指向零值的 []int 2fmt.Println(info) // 输出 &[] 3fmt.Println(*info) // 输出 [] 4fmt.Println(len(*info)) // 输出 0 5fmt.Println(cap(*info)) // 输出 0 6fmt.Println(*info == nil) // 输出 false(切片本身非 nil,但底层数组为 nil)
 
 
- make 的行为 - 作用: - make用于创建并初始化切片(Slice)、映射(map)或通道(channel)。对于切片,- make会分配内存并返回一个初始化后的切片(非指针)。
- 切片内存分配: - 当使用 - make([]T, len, cap)时,会为切片分配内存,并初始化切片的- len和- cap。
- 底层数组会被分配,且所有元素初始化为零值。 
- 如果未指定 - cap,则默认- cap等于- len。
 
- 示例: - 1info := make([]int, 2, 5) // 初始化一个长度为 2、容量为 5 的切片 2fmt.Println(info) // 输出 [0 0] 3fmt.Println(len(info)) // 输出 2 4fmt.Println(cap(info)) // 输出 5 5fmt.Println(info == nil) // 输出 false
 - 操作 - new([]T)- make([]T, len, cap)- 返回值 - 指向零值切片的指针( - *[]T)- 初始化后的切片( - []T)- 底层数组 - nil- 分配内存,元素初始化为零值 - 使用场景 - 需要指针时(较少见) - 直接创建切片时