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-значения.
На первый взгляд мелочь, но такой баг вполне может оказаться в проде.