détection nulle dans Go

165

Je vois beaucoup de code dans Go pour détecter nil, comme ceci:

if err != nil { 
    // handle the error    
}

cependant, j'ai une structure comme celle-ci:

type Config struct {
    host string  
    port float64
}

et config est une instance de Config, quand je fais:

if config == nil {
}

il y a une erreur de compilation, disant: impossible de convertir nil en type Config

Qian Chen
la source
3
Je ne comprends pas pourquoi le port est de type float64?
alamin
2
Ça ne devrait pas l'être. L'API JSON de Go importe n'importe quel nombre de JSON dans float64, je dois convertir le float64 en int.
Qian Chen

Réponses:

179

Le compilateur pointe l'erreur vers vous, vous comparez une instance de structure et nil. Ils ne sont pas du même type, il le considère donc comme une comparaison invalide et vous crie dessus.

Ce que vous voulez faire ici est de comparer un pointeur vers votre instance de configuration à nil, ce qui est une comparaison valide. Pour ce faire, vous pouvez soit utiliser le golang new builtin, soit initialiser un pointeur vers celui-ci:

config := new(Config) // not nil

ou

config := &Config{
                  host: "myhost.com", 
                  port: 22,
                 } // not nil

ou

var config *Config // nil

Ensuite, vous pourrez vérifier si

if config == nil {
    // then
}
Oléiade
la source
5
Je suppose que var config &Config // nildevrait être:var config *Config
Tomasz Plonka
var config *Configplante avec invalid memory address or nil pointer dereference. Peut-être que nous avons besoinvar config Config
kachar
Je comprends que le raisonnement derrière ce choix n'est peut-être pas le vôtre, mais cela n'a aucun sens pour moi que "if! (Config! = Nil)" soit valide mais que "if config == nil" ne l'est pas. Les deux font une comparaison entre le même struct et non-struct.
retorquere le
@retorquere ils sont tous les deux invalides, voir play.golang.org/p/k2EmRcels6 . Que ce soit '! =' Ou '==' ne fait aucune différence; ce qui fait une différence, c'est si config est un struct ou un pointeur vers struct.
stewbasic
Je pense que c'est faux, car c'est toujours faux: play.golang.org/p/g-MdbEbnyNx
Madeo
61

En plus d'Oleiade, consultez les spécifications sur les valeurs nulles :

Lorsque la mémoire est allouée pour stocker une valeur, soit par une déclaration, soit par un appel de make ou new, et qu'aucune initialisation explicite n'est fournie, la mémoire reçoit une initialisation par défaut. Chaque élément d'une telle valeur est défini sur la valeur zéro pour son type: false pour les booléens, 0 pour les entiers, 0,0 pour les flottants, "" pour les chaînes et nil pour les pointeurs, fonctions, interfaces, tranches, canaux et cartes. Cette initialisation se fait de manière récursive, donc par exemple, chaque élément d'un tableau de structures aura ses champs remis à zéro si aucune valeur n'est spécifiée.

Comme vous pouvez le voir, nilla valeur zéro n'est pas pour chaque type mais uniquement pour les pointeurs, les fonctions, les interfaces, les tranches, les canaux et les cartes. C'est la raison pour laquelle config == nilest une erreur et &config == nilne l'est pas.

Pour vérifier si votre struct est non initialisé , vous auriez de vérifier tous les membres de sa valeur zéro respective (par exemple host == "", port == 0etc.) ou un domaine privé qui est défini par une méthode d'initialisation interne. Exemple:

type Config struct {
    Host string  
    Port float64
    setup bool
}

func NewConfig(host string, port float64) *Config {
    return &Config{host, port, true}
}

func (c *Config) Initialized() bool { return c != nil && c.setup }
nemo
la source
4
Suite à ce qui précède, c'est pourquoi time.Timea une IsZero()méthode. Cependant, vous pouvez également le faire var t1 time.Time; if t1 == time.Time{}et vous pouvez également le faire if config == Config{}pour vérifier tous les champs pour vous (l'égalité de structure est bien définie dans Go). Cependant, ce n'est pas efficace si vous avez beaucoup de champs. Et, peut-être que la valeur zéro est une valeur saine et utilisable, donc en passer une n'est pas spécial.
Dave C
1
La fonction Initialized échouera si vous accédez à Config en tant que pointeur. Il pourrait être changé enfunc (c *Config) Initialized() bool { return !(c == nil) }
Sundar
@Sundar dans ce cas, il pourrait être pratique de le faire de cette façon, j'ai donc appliqué le changement. Cependant, normalement, je ne m'attendrais pas à ce que la fin de réception de l'appel de méthode vérifie si elle-même est nulle, car cela devrait être le travail de l'appelant.
nemo
16

J'ai créé un exemple de code qui crée de nouvelles variables en utilisant une variété de façons auxquelles je peux penser. Il semble que les 3 premières méthodes créent des valeurs et les deux dernières créent des références.

package main

import "fmt"

type Config struct {
    host string
    port float64
}

func main() {
    //value
    var c1 Config
    c2 := Config{}
    c3 := *new(Config)

    //reference
    c4 := &Config{}
    c5 := new(Config)

    fmt.Println(&c1 == nil)
    fmt.Println(&c2 == nil)
    fmt.Println(&c3 == nil)
    fmt.Println(c4 == nil)
    fmt.Println(c5 == nil)

    fmt.Println(c1, c2, c3, c4, c5)
}

qui sort:

false
false
false
false
false
{ 0} { 0} { 0} &{ 0} &{ 0}
Qian Chen
la source
6

Vous pouvez également vérifier comme struct_var == (struct{}). Cela ne vous permet pas de comparer à nil mais cela vérifie s'il est initialisé ou non. Soyez prudent lorsque vous utilisez cette méthode. Si votre structure peut avoir des valeurs nulles pour tous ses champs, vous ne passerez pas beaucoup de temps.

package main

import "fmt"

type A struct {
    Name string
}

func main() {
    a := A{"Hello"}
    var b A

    if a == (A{}) {
        fmt.Println("A is empty") // Does not print
    } 

    if b == (A{}) {
        fmt.Println("B is empty") // Prints
    } 
}

http://play.golang.org/p/RXcE06chxE

Thellimiste
la source
3

La spécification du langage mentionne les comportements des opérateurs de comparaison:

opérateurs de comparaison

Dans toute comparaison, le premier opérande doit être assignable au type du deuxième opérande, ou vice versa.


Assignabilité

Une valeur x peut être affectée à une variable de type T ("x est affectable à T") dans l'un de ces cas:

  • Le type de x est identique à T.
  • Les types V et T de x ont des types sous-jacents identiques et au moins l'un des types V ou T n'est pas un type nommé.
  • T est un type d'interface et x implémente T.
  • x est une valeur de canal bidirectionnel, T est un type de canal, les types de x V et T ont des types d'élément identiques, et au moins l'un de V ou T n'est pas un type nommé.
  • x est l'identificateur pré-déclaré nil et T est un type de pointeur, de fonction, de tranche, de carte, de canal ou d'interface.
  • x est une constante non typée représentable par une valeur de type T.
supei
la source
1

Dans Go 1.13 et versions ultérieures, vous pouvez utiliser la Value.IsZerométhode proposée dans le reflectpackage.

if reflect.ValueOf(v).IsZero() {
    // v is zero, do something
}

Outre les types de base, il fonctionne également pour Array, Chan, Func, Interface, Map, Ptr, Slice, UnsafePointer et Struct. Voir ceci pour référence.

Mrpandey
la source