Как в Go происходит сравнение интерфейсов и в каких случаях оно возможно?

Ответ

Сравнение интерфейсов в Go — это важная концепция, которая проверяет как динамический тип, так и динамическое значение, хранящиеся в интерфейсе.

Правила сравнения

Два интерфейсных значения равны, если выполняется одно из условий:

  1. Оба интерфейса равны nil.
  2. Их динамические типы идентичны, и их динамические значения равны в соответствии с правилами сравнения для этого типа.

Интерфейсы не равны, если:

  • Один из них nil, а другой нет.
  • Их динамические типы различаются.
  • Их типы одинаковы, но значения не равны.

Пример сравнения

var a, b interface{}

a = 100
b = 100
fmt.Println(a == b) // true (типы int, значения 100 == 100)

b = int64(100)
fmt.Println(a == b) // false (типы разные: int != int64)

var c interface{} = "hello"
var d interface{} = "hello"
fmt.Println(c == d) // true (типы string, значения равны)

⚠️ Паника при сравнении несравниваемых типов

Попытка сравнить два интерфейса, которые содержат значения несравниваемых типов, вызовет панику во время выполнения.

К несравниваемым типам относятся:

  • Слайсы (slice)
  • Карты (map)
  • Функции (func)
var c interface{} = []int{1, 2}
var d interface{} = []int{1, 2}

// fmt.Println(c == d) // паника: runtime error: comparing uncomparable type []int

Классическая ловушка: nil интерфейс vs интерфейс с nil значением

Интерфейс не равен nil, если в нем хранится значение, даже если само это значение является nil-указателем. Это происходит потому, что у такого интерфейса есть динамический тип.

type MyError struct{}
func (e *MyError) Error() string { return "error" }

func getError() error {
    var err *MyError = nil // Создаем nil-указатель на структуру
    return err             // Возвращаем его как интерфейс error
}

func main() {
    err := getError()
    // err не равен nil, т.к. он содержит тип (*MyError) и значение (nil)
    if err != nil {
        fmt.Printf("Ошибка! Тип: %T, Значение: %vn", err, err)
    }
}
// Вывод:
// Ошибка! Тип: *main.MyError, Значение: <nil>