В каких случаях значение интерфейсного типа в Go равно `nil`?

Ответ

Значение интерфейсного типа в Go равно nil только тогда, когда и его динамический тип, и динамическое значение равны nil.

Интерфейс в Go внутренне представляет собой кортеж из двух указателей: (тип, значение).

Ключевая концепция

  • var i interface{}: Здесь i равен nil, потому что и тип, и значение не установлены.
  • var p *int: Указатель p равен nil.
  • i = p: Теперь интерфейс i не равен nil. Его тип становится *int, а значение — nil.

Это частая «ловушка» в Go, когда функция, возвращающая интерфейс, может вернуть не nil-интерфейс, который содержит nil-указатель.

Пример

package main

import (
    "fmt"
    "reflect"
)

func main() {
    var i interface{} // тип=nil, значение=nil
    fmt.Printf("1. i: (value: %v, type: %T), is nil? %vn", i, i, i == nil)

    var p *int // p - это nil-указатель
    i = p      // тип=*int, значение=nil
    fmt.Printf("2. i: (value: %v, type: %T), is nil? %vn", i, i, i == nil)

    // Как правильно проверить, что значение внутри интерфейса - nil?
    if i != nil && reflect.ValueOf(i).IsNil() {
        fmt.Println("3. Интерфейс не nil, но его внутреннее значение - nil.")
    }
}

// Вывод:
// 1. i: (value: <nil>, type: <nil>), is nil? true
// 2. i: (value: <nil>, type: *int), is nil? false
// 3. Интерфейс не nil, но его внутреннее значение - nil.

Как правильно проверять

  1. Простая проверка if i == nil: Сработает только если и тип, и значение nil.
  2. Проверка через рефлексию reflect.ValueOf(i).IsNil(): Надежный способ проверить, является ли внутреннее значение интерфейса nil (например, nil-указатель, nil-слайс, nil-канал и т.д.). Следует использовать с осторожностью, так как рефлексия медленнее.