Je n'arrive pas à comprendre comment garantir correctement que quelque chose n'est pas nil
dans ce cas:
package main
type shower interface {
getWater() []shower
}
type display struct {
SubDisplay *display
}
func (d display) getWater() []shower {
return []shower{display{}, d.SubDisplay}
}
func main() {
// SubDisplay will be initialized with null
s := display{}
// water := []shower{nil}
water := s.getWater()
for _, x := range water {
if x == nil {
panic("everything ok, nil found")
}
//first iteration display{} is not nil and will
//therefore work, on the second iteration
//x is nil, and getWater panics.
x.getWater()
}
}
Le seul moyen que j'ai trouvé pour vérifier si cette valeur est réellement nil
est en utilisant la réflexion.
Est-ce vraiment un comportement recherché? Ou est-ce que je ne vois aucune erreur majeure dans mon code?
Le problème ici est que shower
est un type interface
. Les types d'interface dans Go contiennent la valeur réelle et son type dynamique. Plus de détails à ce sujet: Les lois de la réflexion #La représentation d'une interface .
La tranche que vous retournez contient 2 valeurs nonnil
. La 2ème valeur est une valeur d'interface, une paire (valeur; type) contenant une valeur nil
et un type *display
. Citant de la Go Language Specification: Comparison operators :
Les valeurs d'interface sont comparables. Deux valeurs d'interface sont égales si elles ont des types dynamiques identiques et des valeurs dynamiques égales ou si les deux ont la valeur
nil
.
Donc, si vous le comparez à nil
, ce sera false
. Si vous le comparez à une valeur d'interface représentant la paire (nil;*display)
, Ce sera true
:
if x == (*display)(nil) {
panic("everything ok, nil found")
}
Cela semble irréalisable car vous devez connaître le type réel de l'interface. Mais notez que vous pouvez utiliser la réflexion pour savoir si une valeur d'interface nonnil
enveloppe une valeur nil
à l'aide de Value.IsNil()
. Vous pouvez voir un exemple de cela sur le Go Playground .
Pourquoi est-il implémenté de cette façon?
Contrairement aux autres types de béton (non-interfaces), les interfaces peuvent contenir des valeurs de différents types de béton (différents types statiques). Le runtime doit connaître le type dynamique ou runtime de la valeur stockée dans une variable de type interface.
Un interface
n'est qu'un ensemble de méthodes, n'importe quel type l'implémente si les mêmes méthodes font partie de ensemble de méthodes du type. Il existe des types qui ne peuvent pas être nil
, par exemple un struct
ou un type personnalisé avec int
comme type sous-jacent. Dans ces cas, vous n'auriez pas besoin de pouvoir stocker une valeur nil
de ce type spécifique.
Mais n'importe quel type comprend également des types concrets où nil
est une valeur valide (par exemple, tranches, cartes, canaux, tous les types de pointeurs), donc afin de stocker la valeur à l'exécution qui satisfait l'interface, il est raisonnable de prendre en charge le stockage de nil
à l'intérieur de l'interface. Mais outre le nil
à l'intérieur de l'interface, nous devons stocker son type dynamique car la valeur nil
ne contient pas de telles informations. L'autre option serait d'utiliser nil
comme valeur d'interface elle-même lorsque la valeur à y stocker est nil
, mais cette solution est insuffisante car elle perdrait les informations de type dynamique.
Certaines personnes disent que les interfaces de Go sont typées dynamiquement, mais cela est trompeur. Ils sont typés statiquement: une variable de type interface a toujours le même type statique, et même si au moment de l'exécution la valeur stockée dans la variable d'interface peut changer de type, cette valeur satisfera toujours l'interface.
En général, si vous souhaitez indiquer nil
pour une valeur de type interface
, utilisez une valeur explicite nil
et vous pourrez ensuite tester l'égalité nil
. L'exemple le plus courant est le type intégré error
qui est une interface avec une méthode. Chaque fois qu'il n'y a pas d'erreur, vous définissez ou renvoyez explicitement la valeur nil
et non la valeur d'une variable d'erreur de type concret (non-interface) (ce qui serait vraiment une mauvaise pratique, voir la démonstration ci-dessous).
Dans votre exemple, la confusion provient des faits suivants:
shower
)shower
mais de type concretAinsi, lorsque vous mettez un type *display
Dans la tranche shower
, une valeur d'interface sera créée, qui est une paire de (valeur; type) où valeur est nil
et tapez est *display
. La valeur à l'intérieur de la paire sera nil
, pas la valeur d'interface elle-même. Si vous mettiez une valeur nil
dans la tranche, alors la valeur d'interface elle-même serait nil
et une condition x == nil
Serait true
.
Démonstration
Voir cet exemple: Playground
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)
}
}
Production:
0 <nil> true
1 <nil> false
2 <nil> false
3 <nil> true
4 <nil> false
Dans le cas 2, un pointeur nil
est renvoyé mais d'abord il est converti en un type d'interface (error
) donc une valeur d'interface est créée qui contient une valeur nil
et le type *MyErr
, Donc la valeur de l'interface n'est pas nil
.
Imaginons une interface comme un pointeur.
Disons que vous avez un pointeur a
et qu'il est nul, ne pointant vers rien.
var a *int // nil
Ensuite, vous avez un pointeur b
et il pointe vers a
.
var b **int
b = &a // not nil
Tu vois ce qui s'est passé? b
pointe sur un pointeur qui ne pointe sur rien. Donc, même si c'est un pointeur nul à la fin de la chaîne, b
pointe vers quelque chose - ce n'est pas nul.
Si vous jetez un œil à la mémoire du processus, cela pourrait ressembler à ceci:
address | name | value
1000000 | a | 0
2000000 | b | 1000000
Voir? a
pointe vers l'adresse 0 (ce qui signifie que c'est nil
) et b
pointe vers l'adresse de a
(1000000).
La même chose s'applique aux interfaces (sauf qu'elles ont l'air un peu différentes en mémoire ).
Comme un pointeur, une interface pointant vers un pointeur nul ne serait pas nulle elle-même .
Ici, voyez par vous-même comment cela fonctionne avec les pointeurs et comment cela fonctionne avec les interfaces .