Maison > Article > développement back-end > golang : Comprendre la différence entre les pointeurs nuls et les interfaces nulles
Je réfléchissais un peu aux différentes manières dont zéro fonctionne en go, et comment parfois, quelque chose peut être à la fois nul et non nul en même temps.
Voici un petit exemple de quelque chose qui peut être un pointeur nul, mais pas une interface nulle. Voyons ce que cela signifie.
Tout d'abord, go a un concept d'interfaces, qui sont similaires, mais pas tout à fait identiques aux interfaces de certains langages orientés objet (go n'est pas la POO selon la plupart des définitions). En go, une interface est un type qui définit des fonctions qu'un autre type doit implémenter pour satisfaire l'interface. Cela nous permet d'avoir plusieurs types concrets pouvant satisfaire une interface de différentes manières.
Par exemple, error est une interface intégrée qui possède une seule méthode. Cela ressemble à ceci :
type error interface { Error() string }
Tout type qui souhaite être utilisé comme erreur doit avoir une méthode appelée Error qui renvoie une chaîne. Par exemple, le code suivant pourrait être utilisé :
type ErrorMessage string func (em ErrorMessage) Error() string { return string(em) } func DoSomething() error { // Try to do something, and it fails. if somethingFailed { var err ErrorMessage = "This failed" return err } return nil } func main() { err := DoSomething() if err != nil { panic(err) } }
Remarquez dans cet exemple que DoSomething renvoie une erreur si quelque chose ne va pas. Nous pouvons utiliser notre type ErrorMessage, car il possède la fonction Error, qui renvoie une chaîne, et implémente donc l'interface d'erreur.
Si aucune erreur ne s'est produite, nous avons renvoyé zéro.
En go, les pointeurs pointent vers une valeur, mais ils peuvent aussi pointer vers aucune valeur, auquel cas le pointeur est nul. Par exemple :
var i *int = nil func main() { if i == nil { j := 5 i = &j } fmt.Println("i is", *i) }
Dans ce cas, la variable i est un pointeur vers un int. Cela commence comme un pointeur nul, jusqu'à ce que nous créions un int et que nous le pointions vers cela.
Étant donné que les types définis par l'utilisateur peuvent avoir des fonctions (méthodes) attachées, nous pouvons également avoir des fonctions pour les pointeurs vers des types. C'est une pratique très courante en go. Cela signifie également que les pointeurs peuvent également implémenter des interfaces. De cette façon, nous pourrions avoir une valeur qui soit une interface non nulle, mais toujours un pointeur nul. Considérez le code suivant :
type TruthGetter interface { IsTrue() bool } func PrintIfTrue(tg TruthGetter) { if tg == nil { fmt.Println("I can't tell if it's true") return } if tg.IsTrue() { fmt.Println("It's true") } else { fmt.Println("It's not true") } }
Tout type qui a une méthode booléenne IsTrue() peut être transmis à PrintIfTrue, mais nil aussi. Donc, nous pouvons faire PrintIfTrue(nil) et cela affichera "Je ne peux pas dire si c'est vrai".
Nous pouvons aussi faire quelque chose de simple comme ceci :
type Truthy bool func (ty Truthy) IsTrue() bool { return bool(ty) } func main() { var ty Truthy = true PrintIfTrue(ty) }
Cela affichera "C'est vrai".
Ou, nous pouvons faire quelque chose de plus compliqué, comme :
type TruthyNumber int func (tn TruthyNumber) IsTrue() bool { return tn > 0 } func main() { var tn TruthyNumber = -4 PrintIfTrue(tn) }
Cela affichera "Ce n'est pas vrai". Aucun de ces exemples n'est un pointeur, et il n'y a donc aucune chance d'obtenir un résultat nul avec l'un ou l'autre de ces types, mais considérez ceci :
type TruthyPerson struct { FirstName string LastName string } func (tp *TruthyPerson) IsTrue() bool { return tp.FirstName != "" && tp.LastName != "" }
Dans ce cas, TruthyPerson n'implémente pas TruthGetter, mais *TruthyPerson le fait. Donc, cela devrait fonctionner :
func main() { tp := &TruthyPerson{"Jon", "Grady"} PrintIfTrue(tp) }
Cela fonctionne car tp est un pointeur vers une TruthyPerson. Cependant, si le pointeur est nul, nous aurons une panique.
func main() { var tp *TruthyPerson PrintIfTrue(tp) }
Cela va paniquer. Cependant, la panique ne se produit pas dans PrintIfTrue. On pourrait penser que tout va bien, car PrintIfTrue vérifie zéro. Mais voici le problème. Il vérifie zéro contre un TruthGetter. En d’autres termes, il recherche une interface nulle, mais pas un pointeur nul. Et dans le bool func (tp *TruthyPerson) IsTrue(), nous ne vérifions pas un zéro. En go, on peut toujours appeler des méthodes sur un pointeur nul, donc la panique se produit là-bas. La solution est en fait assez simple.
func (tp *TruthyPerson) IsTrue() bool { if tp == nil { return false } return tp.FirstName != "" && tp.LastName != "" }
Maintenant, nous recherchons une interface nulle dans PrintIfTrue et un pointeur nul dans func (tp *TruthyPerson) IsTrue() bool. Et il affichera désormais "Ce n'est pas vrai". Nous pouvons voir tout ce code fonctionner ici.
Avec réflexion, nous pouvons apporter une petite modification à PrintIfTrue afin qu'il puisse vérifier à la fois les interfaces nulles et les pointeurs nuls. Voici le code :
func PrintIfTrue(tg TruthGetter) { if tg == nil { fmt.Println("I can't tell if it's true") return } val := reflect.ValueOf(tg) k := val.Kind() if (k == reflect.Pointer || k == reflect.Chan || k == reflect.Func || k == reflect.Map || k == reflect.Slice) && val.IsNil() { fmt.Println("I can't tell if it's true") return } if tg.IsTrue() { fmt.Println("It's true") } else { fmt.Println("It's not true") } }
Ici, nous vérifions d'abord l'interface nil, comme avant. Ensuite, nous utilisons la réflexion pour obtenir le type. chan, func, map et slice peuvent également être nuls, en plus des pointeurs, nous vérifions donc si la valeur est de l'un de ces types, et si c'est le cas, vérifions si elle est nulle. Et si c'est le cas, nous renvoyons également le message "Je ne peux pas dire si c'est vrai". Cela peut ou non être exactement ce que vous voulez, mais c'est une option. Avec ce changement, nous pouvons faire ceci :
func main() { var tp *TruthyPerson PrintIfTrue(tp) }
Vous pouvez parfois voir une suggestion pour quelque chose de plus simple, comme :
// Don't do this if tg == nil && reflect.ValueOf(tg).IsNil() { fmt.Println("I can't tell if it's true") return }
Il y a deux raisons pour lesquelles cela ne fonctionne pas bien. Premièrement, l’utilisation de la réflexion entraîne une surcharge de performances. Si vous pouvez éviter d’utiliser la réflexion, vous devriez probablement le faire. Si nous vérifions d'abord l'interface nulle, nous n'avons pas besoin d'utiliser la réflexion s'il s'agit d'une interface nulle.
La deuxième raison est que reflex.Value.IsNil() paniquera si le type de la valeur n'est pas un type qui peut être nul. C'est pourquoi nous ajoutons le chèque pour le genre. Si nous n'avions pas vérifié le Kind, nous aurions paniqué sur les types Truthy et TruthyNumber.
Donc, tant que nous veillons à vérifier le type en premier, cela affichera désormais "Je ne peux pas dire si c'est vrai", au lieu de "Ce n'est pas vrai". Selon votre point de vue, cela peut être une amélioration. Voici le code complet avec ce changement.
Ceci a été initialement publié sur Dan's Musings
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!