detección nula en Go

165

Veo mucho código en Ir para detectar nulo, así:

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

Sin embargo, tengo una estructura como esta:

type Config struct {
    host string  
    port float64
}

y config es una instancia de Config, cuando lo hago:

if config == nil {
}

hay un error de compilación que dice: no se puede convertir nil para escribir Config

Qian Chen
fuente
3
No entiendo por qué el puerto es de tipo float64?
Alamin
2
No debe ser La API JSON de Go importa cualquier número de JSON a float64, tengo que convertir el float64 a int.
Qian Chen

Respuestas:

179

El compilador le está señalando el error, está comparando una instancia de estructura y nula. No son del mismo tipo, por lo que lo considera como una comparación no válida y le grita.

Lo que desea hacer aquí es comparar un puntero a su instancia de configuración con nil, que es una comparación válida. Para hacerlo, puede usar el nuevo programa incorporado de golang o inicializar un puntero:

config := new(Config) // not nil

o

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

o

var config *Config // nil

Entonces podrás comprobar si

if config == nil {
    // then
}
Oleiade
fuente
55
Supongo que var config &Config // nildebería ser:var config *Config
Tomasz Plonka
var config *Config se estrella con invalid memory address or nil pointer dereference . Tal vez necesitamosvar config Config
kachar
Entiendo el razonamiento detrás de esta elección puede que no sea la suya, pero tiene ningún sentido para mí que "si! (Config! = Nil)" es válida, pero que "si config == nil" no lo es. Ambos están haciendo una comparación entre la misma estructura y no estructura.
retorquere
@retorquere ambos son inválidos, vea play.golang.org/p/k2EmRcels6 . Si es '! =' O '==' no hay diferencia; lo que sí hace la diferencia es si config es una estructura o un puntero a struct.
stewbasic
Creo que esto está mal, ya que siempre es falso: play.golang.org/p/g-MdbEbnyNx
Madeo
61

Además de Oleiade, vea la especificación de valores cero :

Cuando se asigna memoria para almacenar un valor, ya sea a través de una declaración o una llamada de make o new, y no se proporciona una inicialización explícita, la memoria recibe una inicialización predeterminada. Cada elemento de dicho valor se establece en el valor cero para su tipo: falso para booleanos, 0 para enteros, 0.0 para flotantes, "" para cadenas y nulo para punteros, funciones, interfaces, sectores, canales y mapas. Esta inicialización se realiza de forma recursiva, por lo que, por ejemplo, cada elemento de una matriz de estructuras tendrá sus campos a cero si no se especifica ningún valor.

Como puede ver, nilno es el valor cero para cada tipo, sino solo para punteros, funciones, interfaces, sectores, canales y mapas. Esta es la razón por la cual config == niles un error y &config == nilno lo es.

Para comprobar si su estructura está sin inicializar que tendría que comprobar todos los miembros de su respectivo valor de cero (por ejemplo host == "", port == 0, etc.) o tienen un campo privado que se configura mediante un método de inicialización interna. Ejemplo:

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
fuente
44
Además de lo anterior, es por eso que time.Timetiene un IsZero()método. Sin embargo también se puede hacer var t1 time.Time; if t1 == time.Time{}y usted podría también hacer if config == Config{}para comprobar todo el campo para usted (igualdad estructura está bien definido en Ir). Sin embargo, eso no es eficiente si tiene muchos campos. Y, tal vez el valor cero es un valor razonable y utilizable, por lo que pasar uno no es especial.
Dave C
1
La función Inicializada fallará si se accede a Config como puntero. Podría cambiarse afunc (c *Config) Initialized() bool { return !(c == nil) }
Sundar
@Sundar en este caso podría ser conveniente hacerlo de esta manera, así que apliqué el cambio. Sin embargo, normalmente no esperaría que el extremo receptor de la llamada al método verifique si es nula, ya que este debería ser el trabajo de la persona que llama.
nemo
16

He creado un código de muestra que crea nuevas variables usando una variedad de formas en las que puedo pensar. Parece que las primeras 3 formas crean valores, y las dos últimas crean referencias.

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)
}

que salidas:

false
false
false
false
false
{ 0} { 0} { 0} &{ 0} &{ 0}
Qian Chen
fuente
6

También puedes consultar como struct_var == (struct{}). Esto no le permite comparar a cero, pero sí verifica si se inicializa o no. Tenga cuidado al usar este método. Si su estructura puede tener valores cero para todos sus campos, no lo pasará en grande.

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

Thellimist
fuente
3

La especificación del lenguaje menciona los comportamientos de los operadores de comparación:

operadores de comparación

En cualquier comparación, el primer operando debe ser asignable al tipo del segundo operando, o viceversa.


Asignabilidad

Un valor x se puede asignar a una variable de tipo T ("x se puede asignar a T") en cualquiera de estos casos:

  • El tipo de x es idéntico a T.
  • Los tipos V y T de x tienen tipos subyacentes idénticos y al menos uno de V o T no es un tipo con nombre.
  • T es un tipo de interfaz y x implementa T.
  • x es un valor de canal bidireccional, T es un tipo de canal, los tipos V y T de x tienen tipos de elementos idénticos, y al menos uno de V o T no es un tipo con nombre.
  • x es el identificador predefinido nil y T es un puntero, función, segmento, mapa, canal o tipo de interfaz.
  • x es una constante sin tipo representable por un valor de tipo T.
supei
fuente
0

En Go 1.13 y posterior, puede usar el Value.IsZerométodo ofrecido en el reflectpaquete.

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

Además de los tipos básicos, también funciona para Array, Chan, Func, Interface, Map, Ptr, Slice, UnsafePointer y Struct. Vea esto como referencia.

mrpandey
fuente