Под капотом интерфейсы реализованы в виде двух элементов, типа T
и значения V
. V
- это конкретное значение, такое как int
, struct
или указатель, никогда не сам интерфейс, и имеет тип T
. Например, если мы храним int
значение 3 в интерфейсе, результирующее значение интерфейса имеет, схематически, (T = int
, V = 3
). Значение V
также известно как динамическое значение интерфейса, поскольку данная переменная интерфейса может содержать разные значения V
(и соответствующие типы T
) во время исполнения программы.
Значение интерфейса равно nil
, только если V
и T
оба не заданы (T = nil
, V
не задано), в частности, интерфейс nil
всегда будет содержать тип nil
. Если мы сохраним указатель nil
типа *int
внутри значения интерфейса, внутренний тип будет *int
независимо от значения указателя: (T = *int
, V = nil
). Следовательно, такое значение интерфейса будет не nil
, даже если значение указателя V
внутри равно nil
.
Эта ситуация может сбивать с толку и возникает, когда значение nil
хранится внутри значения интерфейса, такого как возвращаемый error
:
func returnsError() error {
var p *MyError = nil
if bad() {
p = ErrBad
}
return p // Всегда будет возвращать не-nil ошибку.
}
Если все идет хорошо, функция возвращает nil
p
, поэтому возвращаемое значение является значением интерфейса error
содержащее (T = *MyError
, V = nil
). Это означает, что если вызывающая сторона сравнивает возвращенную ошибку с nil
, это всегда будет выглядеть так, как будто произошла ошибка, даже если ничего плохого не произошло. Чтобы вернуть правильную nil
error
вызывающей стороне, функция должна возвращать явный nil
:
func returnsError() error {
if bad() {
return ErrBad
}
return nil
}
Это хорошая идея для функций, которые возвращают ошибки всегда использовать тип error
в их сигнатуре (как мы сделали выше), а не конкретный тип, такой как *MyError
, чтобы гарантировать, что ошибка создана правильно. В качестве примера,
os.Open
возвращает error
, хотя, если не nil
, она всегда конкретного типа *os.PathError
.
Ситуации, подобные описанным здесь, могут возникать всякий раз, когда используются интерфейсы. Просто имейте в виду, что если какое-то конкретное значение было сохранено в интерфейсе, интерфейс не будет nil
.
Читайте также:
- Go FAQ: Почему в Go нет наследования типов?
- Эффективный Go: интерфейсы, преобразования
- Основы Go: интерфейсы
Комментариев нет:
Отправить комментарий