go: почему nil != nil

Посмотрим на такой код: type MyError struct { message string } func (e *MyError) Error() string { return e.message } func getError() error { var err *MyError = nil return err } func main() { err := getError() fmt.Println(err == nil) }

Что выведет программа? false

Хотя внутри getError() переменная err равна nil.

Почему? Дело в том, как устроен интерфейс

Интерфейсное значение в Go содержит две составляющие: динамический тип; динамическое значение.

Когда мы делаем: var err *MyError = nil return err

Go преобразует typed nil pointer в значение интерфейса. Получается примерно такая картина: interface error ┌───────────────┐ │ dynamic type: MyError│ │ dynamic value: nil │ └───────────────┘

Сам интерфейс уже не равен nil, потому что в нём есть информация о динамическом типе.

Для сравнения: var err error = nil fmt.Println(err == nil) // true

Здесь нет ни динамического типа, ни значения.

Где это может выстрелить?

Например, в функции, которая возвращает ошибку: func validate() error { var err *MyError if somethingWrong() { err = &MyError{ message: "something went wrong", } } return err }

Если ошибки нет, err останется nil. Но после возврата через интерфейс вызывающий код получит ненулевой error.

И такая проверка не сработает: if err != nil { // Сюда попадём, даже если указатель внутри был nil }

Как правильно?

Если ошибки нет, возвращайте именно nil: func validate() error { if somethingWrong() { return &MyError{ message: "something went wrong", } } return nil }

Не нужно возвращать nil-указатель через интерфейс.

Главное правило: В go интерфейс, содержащий typed nil, сам по себе не является nil.

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

На первый взгляд мелочь, но такой баг вполне может оказаться в проде.