go深坑之interface与nil

问题演示

这里演示实际项目中可能出现的场景,IRedisCon是接口,RedisCon是接口的实现,代码中通过getRedisCon()方法获取redis连接对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 接口类型
type IRedisCon interface {
}
// IRedisCon的具体实现
type RedisCon struct {
}
//获取RedisCon类型数据
func getRedisCon() *RedisCon {
return nil
}
func main() {
var redisCon IRedisCon
redisCon = getRedisCon()
fmt.Println(redisCon == nil)
}

运行代码后发现redisCon始终不等于nil,这就是go语言的一个深坑—interface与nil

分析

我们就接口变量与赋予接口变量的值做以下实验,看看判定结果如何

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 实验1: 具体类型指针nil 与 接口
var a *int = nil
var i interface{}
i = a
fmt.Println(i, i == nil) //false 具体类型赋值给接口 接口不为nil 不能与nil对比
// 实验2: 无类型nil与接口
var i2 interface{}
i2 = nil
fmt.Println(i2, i2 == nil) //true 接口本身为nil 接口可与nil对比
// 实验3: 接口nil 与 接口
var i3 interface{}
var i4 interface{}
i3 = nil
i4 = i3
fmt.Println(i4, i4 == nil) //true 接口赋值接口nil 接口可与nil对比

解决方案

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//解决办法1 使用反射
fmt.Println(isNil(i), isNil(i2), isNil(i4))
func isNil(i interface{}) bool {
return i == nil || reflect.ValueOf(i).IsNil()
}
//思考:
//依旧存在的问题 1.对struct array等值类型会panic 2.使用反射,对性能有损耗
//var i5 Student
//var i6 interface{}
//i6 = i5
//fmt.Println(i6, isNil(i6)) //直接报错panic: reflect: call of reflect.Value.IsNil on struct Value 因为go语法上不允许struct为nil
//解决方法2 根据实验3可得出结论--接口与接口赋值可以判定nil。将getRedisCon返回值由*RedisCon改为IRedisCon后nil判定正确
func getRedisCon() IRedisCon {
return nil
}
思考: 因为go奉行的是组合的思想,一个具体结构体可能实现了N个接口,返回接口后外部逻辑可能还需要断言等等,能否减轻这部分的负担?
//解决办法3 go支持多返回值,那么就有这么一种设计思路--`不通过值来判定是否返回正确,而是用专门的err参数表示返回正确与否`

未完待续—这里引发出一个实践上的思考: go方法返回具体值好还是返回接口更好?

参考资料

https://studygolang.com/articles/10635
https://medium.com/@mangatmodi/go-check-nil-interface-the-right-way-d142776edef1