Перейти «Интерфейс, содержащий нулевой указатель, не является нулевым интерфейсом»

Go

Недавно я наступил на глубокую яму в проекте - "Интерфейс, содержащий 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 с примером кода.

Справочная статья:

Первая большая яма Голанга

Обсуждение «Интерфейс, содержащий нулевой указатель, не является нулевым интерфейсом»