Ответ 1
Проблема здесь в том, что shower
является типом interface
. Типы интерфейса в Go содержат фактическое значение и его динамический тип. Подробнее об этом: Законы отражения # Представление интерфейса.
Срез вы возвращаетесь содержит 2 non- nil
значения. Второе значение - это значение интерфейса, пара (значение; тип), содержащая nil
значение, и тип *display
. Цитирование из спецификации языка Go: Операторы сравнения:
Значения интерфейса сопоставимы. Два значения интерфейса равны, если они имеют идентичные динамические типы и одинаковые динамические значения или оба имеют значение
nil
.
Так что, если вы сравните это с nil
, это будет false
. Если вы сравните его со значением интерфейса, представляющим пару (nil;*display)
, оно будет true
:
if x == (*display)(nil) {
panic("everything ok, nil found")
}
Это кажется невозможным, так как вам нужно знать фактический тип интерфейса. Но обратите внимание, что вы можете использовать отражение, чтобы сказать, обнуляет ли значение интерфейса non- nil
значение nil
используя Value.IsNil()
. Вы можете увидеть пример этого на игровой площадке Go.
Почему это реализовано таким образом?
Интерфейсы в отличие от других конкретных типов (интерфейсы non-) могут содержать значения разных конкретных типов (разных статических типов). Среда выполнения должна знать динамический или тип времени выполнения значения, хранящегося в переменной типа интерфейса.
interface
- это просто набор методов, любой тип реализует его, если те же методы являются частью набора методов этого типа. Существуют типы, которые не могут быть nil
, например, struct
или пользовательский тип с int
качестве базового типа. В этих случаях вам не нужно будет хранить nil
значение этого конкретного типа.
Но любой тип также включает в себя конкретные типы, где nil
является допустимым значением (например, срезы, карты, каналы, все типы указателей), поэтому для сохранения значения во время выполнения, которое удовлетворяет интерфейсу, целесообразно поддерживать хранение nil
внутри интерфейса. Но кроме nil
внутри интерфейса мы должны хранить его динамический тип, так как значение nil
не несет такой информации. Альтернативным вариантом будет использование nil
качестве самого значения интерфейса, когда значение, которое будет храниться в нем, равно nil
, но этого решения недостаточно, поскольку оно потеряло бы информацию о динамическом типе.
Некоторые люди говорят, что интерфейсы Go имеют динамическую типизацию, но это вводит в заблуждение. Они имеют статическую типизацию: переменная типа интерфейса всегда имеет один и тот же статический тип, и хотя во время выполнения значение, сохраненное в переменной интерфейса, может изменить тип, это значение всегда будет удовлетворять интерфейсу.
В общем, если вы хотите указать nil
для значения типа interface
, используйте явное значение nil
а затем вы можете проверить на равенство nil
. Наиболее распространенным примером является встроенный тип error
который представляет собой интерфейс с одним методом. Всякий раз, когда ошибки нет, вы явно устанавливаете или возвращаете значение nil
а не значение какой-то конкретной переменной (типа интерфейса non-) (что было бы очень плохой практикой, см. Демонстрацию ниже).
В вашем примере путаница возникает из фактов, которые:
- вы хотите иметь значение в качестве типа интерфейса (
shower
) - но значение, которое вы хотите сохранить в срезе, относится не к типу
shower
а к конкретному типу
Поэтому, когда вы *display
тип *display
в shower
часть, будет создано значение интерфейса, которое представляет собой пару (значение; тип), где значение равно nil
а тип равно *display
. Значение внутри пары будет nil
, а не само значение интерфейса. Если вы поместите значение nil
в срез, тогда само значение интерфейса будет равно nil
и условие x == nil
будет true
.
демонстрация
Посмотрите этот пример: Детская площадка
type MyErr string
func (m MyErr) Error() string {
return "big fail"
}
func doSomething(i int) error {
switch i {
default:
return nil // This is the trivial true case
case 1:
var p *MyErr
return p // This will be false
case 2:
return (*MyErr)(nil) // Same as case 1
case 3:
var err error // Zero value is nil for the interface
return err // This will be true because err is already interface type
case 4:
var p *MyErr
return error(p) // This will be false because the interface points to a
// nil item but is not nil itself.
}
}
func main() {
for i := 0; i <= 4; i++ {
err := doSomething(i)
fmt.Println(i, err, err == nil)
}
}
Выход:
0 <nil> true
1 <nil> false
2 <nil> false
3 <nil> true
4 <nil> false
В случае 2 возвращается nil
указатель, но сначала он преобразуется в тип интерфейса (error
), поэтому создается значение интерфейса, которое содержит значение nil
и тип *MyErr
, поэтому значение интерфейса не равно nil
.