detecção nula no Go

165

Vejo muito código no Go para detectar nada, assim:

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

no entanto, eu tenho uma estrutura como esta:

type Config struct {
    host string  
    port float64
}

e config é uma instância do Config, quando:

if config == nil {
}

há erro de compilação, dizendo: não é possível converter nil para digitar Config

Qian Chen
fonte
3
Eu não entendo por que porta é do tipo float64?
Alamin
2
Não deveria ser. A API JSON do Go importa qualquer número de JSON para float64, tenho que converter o float64 em int.
Qian Chen 29/11

Respostas:

179

O compilador está apontando o erro para você; você está comparando uma instância de estrutura e nada. Eles não são do mesmo tipo, por isso considera uma comparação inválida e gritam com você.

O que você deseja fazer aqui é comparar um ponteiro com sua instância de configuração com zero, que é uma comparação válida. Para fazer isso, você pode usar o golang new builtin ou inicializar um ponteiro para ele:

config := new(Config) // not nil

ou

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

ou

var config *Config // nil

Então você poderá verificar se

if config == nil {
    // then
}
Oleiade
fonte
5
Acho que var config &Config // nildeveria ser:var config *Config
Tomasz Plonka 08/09
var config *Configtrava com invalid memory address or nil pointer dereference. Talvez precisemosvar config Config
Kachar
Eu entendo o raciocínio por trás dessa escolha pode não ser o seu, mas não faz nenhum sentido para mim que "se! (Config! = Nil)" é válido, mas que "se configuração == nil" não é. Ambos estão fazendo uma comparação entre a mesma estrutura e a não-estrutura.
retorquere
@retorquere, ambos são inválidos, consulte play.golang.org/p/k2EmRcels6 . Se é '! =' Ou '==' não faz diferença; o que faz a diferença é se a configuração é uma estrutura ou um ponteiro para a estrutura.
stewbasic
Eu acho que isso está errado, como sempre é falso: play.golang.org/p/g-MdbEbnyNx
Madeo
61

Além da Oleiade, consulte a especificação sobre valores zero :

Quando a memória é alocada para armazenar um valor, por meio de uma declaração ou uma chamada de marca ou nova, e nenhuma inicialização explícita é fornecida, a memória recebe uma inicialização padrão. Cada elemento desse valor é definido com o valor zero para seu tipo: false para booleanos, 0 para números inteiros, 0,0 para números flutuantes "" para seqüências de caracteres e nulo para ponteiros, funções, interfaces, fatias, canais e mapas. Essa inicialização é feita recursivamente, portanto, por exemplo, cada elemento de uma matriz de estruturas terá seus campos zerados se nenhum valor for especificado.

Como você pode ver, nilnão é o valor zero para todos os tipos, mas apenas para ponteiros, funções, interfaces, fatias, canais e mapas. Esta é a razão pela qual config == nilé um erro e &config == nilnão é.

Para verificar se o struct é inicializado você teria que verificar cada membro de seu respectivo valor zero (por exemplo host == "", port == 0, etc.) ou ter um campo privado, que é definido por um método de inicialização interno. Exemplo:

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
fonte
4
Além do acima, é por isso que time.Timetem um IsZero()método. No entanto, você também pode fazer var t1 time.Time; if t1 == time.Time{}e também podeif config == Config{} verificar todo o campo (a igualdade de estrutura está bem definida em Ir). No entanto, isso não é eficiente se você tiver muitos campos. E, talvez o valor zero seja um valor sensato e utilizável, portanto, passar um não é especial.
Dave C
1
A função Initialized falhará se Config como um ponteiro for acessado. Poderia ser alterado parafunc (c *Config) Initialized() bool { return !(c == nil) }
Sundar 6/17
@ Sundar, neste caso, pode ser conveniente fazê-lo dessa maneira, então apliquei a alteração. No entanto, normalmente eu não esperaria que o final de recebimento da chamada de método verifique se ele é nulo, pois esse deve ser o trabalho do chamador.
nemo
16

Eu criei um código de exemplo que cria novas variáveis ​​usando várias maneiras em que consigo pensar. Parece que as três primeiras maneiras de criar valores e as duas últimas criam referências.

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

quais saídas:

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

Você também pode verificar como struct_var == (struct{}). Isso não permite comparar com zero, mas verifica se foi inicializado ou não. Tenha cuidado ao usar este método. Se sua estrutura puder ter valores zero para todos os seus campos, você não terá muito tempo.

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
fonte
3

A especificação da linguagem menciona os comportamentos dos operadores de comparação:

operadores de comparação

Em qualquer comparação, o primeiro operando deve ser atribuído ao tipo do segundo operando, ou vice-versa.


Atribuição

Um valor x é atribuível a uma variável do tipo T ("x é atribuível a T") em qualquer um destes casos:

  • o tipo de x é idêntico a T.
  • os tipos V e T de x têm tipos subjacentes idênticos e pelo menos um de V ou T não é um tipo nomeado.
  • T é um tipo de interface ex implementa T.
  • x é um valor de canal bidirecional, T é um tipo de canal, os tipos V e T de x têm tipos de elementos idênticos e pelo menos um de V ou T não é um tipo nomeado.
  • x é o identificador pré-declarado nulo e T é um tipo de ponteiro, função, fatia, mapa, canal ou interface.
  • x é uma constante sem tipo representável por um valor do tipo T.
supei
fonte
0

No Go 1.13 e posterior, você pode usar o Value.IsZerométodo oferecido no reflectpacote.

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

Além dos tipos básicos, ele também funciona para Array, Chan, Func, Interface, Mapa, Ptr, Slice, UnsafePointer e Struct. Veja isto para referência.

Mrpandey
fonte