Недавно я наступил на глубокую яму в проекте - "Интерфейс, содержащий nil указатели в Golang, не является nil интерфейсом". Феномен в том, что функция возвращает nil объекту, а интерфейс используется для получения возвращаемого значения функция, чтобы судить, что это не ноль. Обобщите и поделитесь им.Если вы не очень хорошо понимаете это предложение, рекомендуется внимательно посмотреть на пример кода ниже, чтобы не наступить на яму при написании кода в будущем.
Пример 1
Давайте сначала посмотрим на этот код, вы думаете, есть проблема?
type IPeople interface {
hello()
}
type People struct {
}
func (p *People) hello() {
fmt.Println("github.com/meetbetter")
}
func errFunc1(in int) *People {
if in == 0 {
fmt.Println("importantFunc返回了一个nil")
return nil
} else {
fmt.Println("importantFunc返回了一个非nil值")
return &People{}
}
}
func main() {
var i IPeople
in := 0
i = errFunc1(in)
if i == nil {
fmt.Println("哈,外部接收到也是nil")
} else {
fmt.Println("咦,外部接收到不是nil哦")
fmt.Printf("%v, %T\n", i, i)
}
}
Результат выполнения этого кода:
importantFunc返回了一个nil
咦,外部接收到不是nil哦
<nil>, *main.People
Видно, что возвращаемое значение, полученное в основной функции, не равно nil, оно, очевидно, nil возвращается в функции errFunc1(), почему оно не nil получено в основной функции?
Это потому, что: присвоение nil*People
позже*People
Назначить интерфейсу,*People
Это указатель на nil, но когда он назначается интерфейсу, только значение в интерфейсе равно nil, но информация о типе в интерфейсе*main.People
вместо nil, так что этот интерфейс не nil.
Да, тип интерфейса в Golang содержит две части информации - информацию о значении и информацию о типе. Интерфейс равен нулю только тогда, когда комбинированный тип значения интерфейса равен нулю. Базовую реализацию интерфейса можно увидеть в анализе исходного кода позже. .
Давайте сначала рассмотрим правильный способ обработки возвращаемого значения интерфейса, который заключается в прямом присвоении nil интерфейсу:
func rightFunc(in int) IPeople {
if in == 0 {
fmt.Println("importantFunc返回了一个nil")
return nil
} else {
fmt.Println("importantFunc返回了一个非nil值")
return &People{}
}
}
Пример 2
Следующий код более наглядно демонстрирует一个包含nil指针的接口不是nil接口
Вывод:
type IPeople interface {
hello()
}
type People struct {
}
func (p *People) hello() {
fmt.Println("github.com/meetbetter")
}
//错误:将nil的people给空接口后接口就不为nil,因为interface中的value为nil但type不为nil
func errFunc() *People {
return nil
}
//正确处理返回nil给接口的方法,返回时go就确定了接口是不是nil
func rightFunc() IPeople {
return nil
}
func main() {
var i IPeople
i = errFunc()
if i == nil { //想通过接口是否为nil来判断故障,却始终判断接口非空
fmt.Println("errFunc对了哦,外部接收到也是nil")
fmt.Println(reflect.TypeOf(i))
} else {
fmt.Println("errFunc错了咦,外部接收到不是nil哦")
fmt.Println(reflect.TypeOf(i))
}
i = rightFunc()
if i == nil {
fmt.Println("rightFunc对了哦,外部接收到也是nil")
fmt.Println(reflect.TypeOf(i))
} else {
fmt.Println("rightFunc错了咦,外部接收到不是nil哦")
fmt.Println(reflect.TypeOf(i))
}
}
Выходной результат:
errFunc错了咦,外部接收到不是nil哦
*main.People
rightFunc对了哦,外部接收到也是nil
<nil>
Базовая реализация интерфейса
Следующая информация в комментариях взята из справочной статьи. Из базовой реализации интерфейса видно, что iface имеет на один уровень структуры itab больше, чем eface. Itab хранит информацию о _type и []fun набор методов, поэтому даже если точки данных на nil, это не значит, что интерфейс нулевой, также учитывается информация _type.
type eface struct { //空接口
_type *_type //类型信息
data unsafe.Pointer //指向数据的指针(go语言中特殊的指针类型unsafe.Pointer类似于c语言中的void*)
}
type iface struct { //带有方法的接口
tab *itab //存储type信息还有结构实现方法的集合
data unsafe.Pointer //指向数据的指针(go语言中特殊的指针类型unsafe.Pointer类似于c语言中的void*)
}
type _type struct {
size uintptr //类型大小
ptrdata uintptr //前缀持有所有指针的内存大小
hash uint32 //数据hash值
tflag tflag
align uint8 //对齐
fieldalign uint8 //嵌入结构体时的对齐
kind uint8 //kind 有些枚举值kind等于0是无效的
alg *typeAlg //函数指针数组,类型实现的所有方法
gcdata *byte
str nameOff
ptrToThis typeOff
}
type itab struct {
inter *interfacetype //接口类型
_type *_type //结构类型
link *itab
bad int32
inhash int32
fun [1]uintptr //可变大小 方法集合
}
Полный код выше организован вПроект Github-Learn Golang с примером кода.
Справочная статья:
Обсуждение «Интерфейс, содержащий нулевой указатель, не является нулевым интерфейсом»