Maison > développement back-end > Golang > le corps du texte

golang : Comprendre la différence entre les pointeurs nuls et les interfaces nulles

WBOY
Libérer: 2024-07-18 08:51:19
original
736 Les gens l'ont consulté

golang: Understanding the difference between nil pointers and nil interfaces

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.

Interfaces

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
}
Copier après la connexion

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)
    }
}
Copier après la connexion

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.

Pointeurs

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)
}
Copier après la connexion

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.

Pointeurs et interfaces

É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")
    }
}
Copier après la connexion

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)
}
Copier après la connexion

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)
}
Copier après la connexion

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 != ""
}
Copier après la connexion

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)
}
Copier après la connexion

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)
}
Copier après la connexion
Copier après la connexion

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 != ""
}
Copier après la connexion

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.

Bonus : vérifiez les deux zéros à la fois avec réflexion

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")
    }
}
Copier après la connexion

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)
}
Copier après la connexion
Copier après la connexion

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
}
Copier après la connexion

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!

source:dev.to
Déclaration de ce site Web
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn
Tutoriels populaires
Plus>
Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal
À propos de nous Clause de non-responsabilité Sitemap
Site Web PHP chinois:Formation PHP en ligne sur le bien-être public,Aidez les apprenants PHP à grandir rapidement!