Интерфейс и nil-указатель
В Go интерфейс хранит пару значений: динамический тип и динамическое значение.
Интерфейс считается nil только тогда, когда оба этих поля равны nil.
Если в интерфейс сохранить nil-указатель на конкретный тип,
то динамический тип становится известным, и интерфейс перестает быть nil,
даже если его значение равно nil.
Это одна из самых частых ловушек в Go, которая приводит к неожиданному поведению
при проверке if err == nil, если error — это интерфейс,
а возвращаемая ошибка реализована через указатель на структуру.
Синтаксис
Объявление переменной типа интерфейс:
var i interface{} // nil-интерфейс
var p *int // nil-указатель
i = p // i больше не nil
Пример
Рассмотрим базовый пример, демонстрирующий разницу между nil-интерфейсом и интерфейсом, содержащим nil-указатель:
package main
import "fmt"
func main() {
var i interface{} // nil-интерфейс
fmt.Println(i == nil)
var p *int // nil-указатель
i = p
fmt.Println(i == nil)
}
Результат выполнения кода:
true
false
Пример
Теперь рассмотрим более жизненный пример с интерфейсом error.
Создадим свою структуру ошибки и посмотрим, как поведет себя проверка
на nil:
package main
import "fmt"
type MyError struct {
msg string
}
func (e *MyError) Error() string {
return e.msg
}
func getError() error {
var e *MyError = nil
return e
}
func main() {
err := getError()
if err == nil {
fmt.Println("err is nil")
} else {
fmt.Println("err is not nil")
}
}
Результат выполнения кода:
"err is not nil"
Несмотря на то, что мы вернули nil-указатель,
функция getError возвращает интерфейс error,
в котором сохранен тип *MyError и значение nil.
Поэтому интерфейс не равен nil.
Пример
Правильный способ обработки таких ситуаций — возвращать nil
только в том случае, если ошибки действительно нет:
package main
import "fmt"
type MyError struct {
msg string
}
func (e *MyError) Error() string {
return e.msg
}
func getError(flag bool) error {
if flag {
return &MyError{msg: "something went wrong"}
}
return nil // возвращаем именно nil, а не nil-указатель
}
func main() {
err := getError(false)
if err == nil {
fmt.Println("err is nil")
} else {
fmt.Println("err is not nil")
}
}
Результат выполнения кода:
"err is nil"
Смотрите также
-
понятие
nil-интерфейса,
который не содержит ни типа, ни значения -
механизм
type assertion,
позволяющий извлечь динамическое значение из интерфейса -
конструкцию
type switch,
которая выполняет действия в зависимости от динамического типа -
интерфейс
error,
который является стандартным интерфейсом для представления ошибок