Что такое nil интерфейс?go-41

Nil-интерфейс — это особое состояние интерфейса в Go, которое часто вызывает путаницу у разработчиков. Давайте разберём это понятие детально.

Два вида "nil" в интерфейсах

В Go существует два принципиально разных случая, когда интерфейс можно считать "nil":

1. Интерфейс без значения и без типа

var i interface{}
fmt.Println(i == nil) // true

В этом случае:

  • Указатель на тип (_type в eface или itab в iface) равен nil
  • Указатель на данные (data) равен nil

2. Интерфейс с nil-значением, но с типом

var s *string        // s == nil
var i interface{} = s
fmt.Println(i == nil) // false!

Здесь:

  • У интерфейса есть информация о типе (*string)
  • Но значение этого типа равно nil

Почему это важно?

Такое поведение может приводить к неожиданным ошибкам:

func doSomething(v interface{}) {
    if v != nil {
        // Этот код выполнится, даже если v содержит nil-указатель!
        fmt.Println("Not nil!", reflect.TypeOf(v))
    }
}

var s *string
doSomething(s) // Выведет "Not nil! *string"

Как правильно проверять nil-интерфейс?

Для полной проверки можно использовать рефлексию:

func isNil(i interface{}) bool {
    if i == nil {
        return true
    }
    val := reflect.ValueOf(i)
    return val.Kind() == reflect.Ptr && val.IsNil()
}

Под капотом

Для интерфейсов с методами (iface):

type iface struct {
    tab  *itab          // nil для истинного nil-интерфейса
    data unsafe.Pointer // nil в обоих случаях
}

Для пустых интерфейсов (eface):

type eface struct {
    _type *_type         // nil для истинного nil-интерфейса
    data  unsafe.Pointer // nil в обоих случаях
}

Практические последствия

  1. Ошибки — частый источник багов, когда функция проверяет err == nil, но возвращается error интерфейс с nil-значением:
func returnsError() error {
    var p *MyError // nil
    return p       // != nil!
}
  1. JSON сериализация — nil-интерфейс и nil-значение в интерфейсе сериализуются по-разному.

Резюмируем

  • Истинный nil-интерфейс — когда и тип, и значение отсутствуют
  • Интерфейс может содержать nil-значение конкретного типа — тогда он не равен nil
  • Это различие важно учитывать при проверках и работе с ошибками
  • Для полной проверки может потребоваться рефлексия
  • Поведение обусловлено внутренним представлением интерфейсов (iface/eface)