wykrywanie zera w Go

165

Widzę dużo kodu w Go, aby wykryć zero, na przykład:

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

jednak mam taką strukturę:

type Config struct {
    host string  
    port float64
}

a config to instancja Config, kiedy robię:

if config == nil {
}

występuje błąd kompilacji, który mówi: nie można przekonwertować nil na typ Config

Qian Chen
źródło
3
Nie rozumiem, dlaczego port jest typu float64?
alamin
2
Nie powinno. Go's JSON api importuje dowolną liczbę z JSON do float64, muszę przekonwertować float64 na int.
Qian Chen

Odpowiedzi:

179

Kompilator wskazuje na błąd, porównujesz instancję struktury i zero. Nie są tego samego typu, więc uważa to za nieprawidłowe porównanie i wrzeszczy na ciebie.

To, co chcesz tutaj zrobić, to porównać wskaźnik do twojej instancji config do zera, co jest poprawnym porównaniem. Aby to zrobić, możesz albo użyć nowego wbudowanego golang , albo zainicjować do niego wskaźnik:

config := new(Config) // not nil

lub

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

lub

var config *Config // nil

Wtedy będziesz mógł sprawdzić, czy

if config == nil {
    // then
}
Oleiade
źródło
5
Chyba var config &Config // nilpowinno być:var config *Config
Tomasz Płonka
var config *Configawarie z invalid memory address or nil pointer dereference. Może potrzebujemyvar config Config
kachar
Rozumiem, że uzasadnienie tego wyboru może nie być Twoje, ale nie ma dla mnie sensu, że „if! (Config! = Nil)” jest poprawne, ale „if config == nil” nie. Obie robią porównanie między tą samą strukturą a niestruktem.
powtórz
@retorquere oba są nieprawidłowe, zobacz play.golang.org/p/k2EmRcels6 . Czy to „! =” Czy „==” nie ma znaczenia; różnica polega na tym, czy config jest strukturą, czy wskaźnikiem do struktury.
stewbasic
Myślę, że to źle, ponieważ zawsze jest fałszywe: play.golang.org/p/g-MdbEbnyNx
Madeo
61

Oprócz Oleiade zapoznaj się ze specyfikacją dotyczącą wartości zerowych :

Gdy pamięć jest przydzielana do przechowywania wartości, za pośrednictwem deklaracji lub wywołania make lub new i nie jest dostarczana jawna inicjalizacja, pamięć otrzymuje domyślną inicjalizację. Każdy element takiej wartości jest ustawiany na zero dla swojego typu: false dla wartości logicznych, 0 dla liczb całkowitych, 0,0 dla liczb zmiennoprzecinkowych, "" dla łańcuchów i zero dla wskaźników, funkcji, interfejsów, wycinków, kanałów i map. Ta inicjalizacja jest wykonywana rekurencyjnie, więc na przykład każdy element tablicy struktur będzie miał wyzerowane pola, jeśli nie zostanie określona żadna wartość.

Jak widać, nilnie jest to wartość zerowa dla każdego typu, ale tylko dla wskaźników, funkcji, interfejsów, wycinków, kanałów i map. To jest powód, dla którego config == niljest to błąd, a &config == nilnie jest.

Aby sprawdzić, czy struktura jest niezainicjowanymi trzeba by sprawdzić każdy element do odpowiedniej wartości zerowej (np host == "", port == 0etc.) lub dysponują pola, która jest tworzona przez wewnętrzne metody inicjalizacji. Przykład:

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
źródło
4
W związku z powyższym, dlatego time.Timema IsZero()metodę. Jednak można również zrobić var t1 time.Time; if t1 == time.Time{}i mógł również zrobić if config == Config{}, aby sprawdzić wszystkie pola dla Ciebie (równość struktura jest dobrze zdefiniowane w Go). Jednak nie jest to wydajne, jeśli masz dużo pól. I być może wartość zero jest rozsądną i użyteczną wartością, więc przekazywanie jedynki nie jest czymś wyjątkowym.
Dave C
1
Zainicjowana funkcja zakończy się niepowodzeniem, jeśli zostanie uzyskany dostęp do programu Config jako wskaźnika. Można to zmienić nafunc (c *Config) Initialized() bool { return !(c == nil) }
Sundar
@Sundar w tym przypadku może być wygodnie zrobić to w ten sposób, więc zastosowałem zmianę. Jednak normalnie nie spodziewałbym się, że odbierający koniec wywołania metody sprawdzi, czy sam jest zerowy, ponieważ powinno to być zadaniem wywołującego.
nemo
16

Stworzyłem przykładowy kod, który tworzy nowe zmienne na wiele różnych sposobów. Wygląda na to, że pierwsze 3 sposoby tworzą wartości, a dwa ostatnie tworzą odwołania.

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

które wyjścia:

false
false
false
false
false
{ 0} { 0} { 0} &{ 0} &{ 0}
Qian Chen
źródło
6

Możesz też sprawdzić jak struct_var == (struct{}). To nie pozwala na porównywanie do zera, ale sprawdza, czy jest zainicjowany, czy nie. Zachowaj ostrożność podczas korzystania z tej metody. Jeśli twoja struktura może mieć zerowe wartości dla wszystkich swoich pól, nie będziesz się dobrze bawić.

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
źródło
3

Specyfikacja języka wspomina o zachowaniach operatorów porównania:

operatory porównania

W każdym porównaniu pierwszy operand musi być przypisany do typu drugiego argumentu lub odwrotnie.


Przypisywalność

Wartość x można przypisać do zmiennej typu T („x można przypisać do T”) w każdym z następujących przypadków:

  • x jest identyczny z T.
  • x typy V i T mają identyczne typy bazowe, a co najmniej jeden z V lub T nie jest nazwanym typem.
  • T jest typem interfejsu, a x implementuje T.
  • x to dwukierunkowa wartość kanału, T to typ kanału, x typy V i T mają identyczne typy elementów, a przynajmniej jeden z V lub T nie jest nazwanym typem.
  • x to wstępnie zadeklarowany identyfikator nil, a T to wskaźnik, funkcja, wycinek, mapa, kanał lub typ interfejsu.
  • x jest nietypową stałą reprezentowaną przez wartość typu T.
supei
źródło
0

W Go 1.13 i późniejszych możesz użyć Value.IsZerometody oferowanej w reflectpakiecie.

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

Oprócz podstawowych typów działa również dla Array, Chan, Func, Interface, Map, Ptr, Slice, UnsafePointer i Struct. Zobacz to w celach informacyjnych.

mrpandey
źródło