deteksi nil di Go

165

Saya melihat banyak kode di Go untuk mendeteksi nihil, seperti ini:

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

Namun, saya memiliki struct seperti ini:

type Config struct {
    host string  
    port float64
}

dan config adalah turunan dari Config, ketika saya melakukannya:

if config == nil {
}

ada kesalahan kompilasi, mengatakan: tidak dapat mengonversi nil untuk mengetik Config

Qian Chen
sumber
3
Saya tidak mengerti mengapa port berjenis float64?
alamin
2
Seharusnya tidak. Api JSON Go mengimpor angka dari JSON ke float64, saya harus mengubah float64 menjadi int.
Qian Chen

Jawaban:

179

Compiler menunjuk kesalahan kepada Anda, Anda membandingkan contoh struktur dan nil. Mereka bukan dari jenis yang sama sehingga menganggapnya sebagai perbandingan yang tidak valid dan berteriak pada Anda.

Yang ingin Anda lakukan di sini adalah membandingkan sebuah pointer ke instance config Anda dengan nil, yang merupakan perbandingan yang valid. Untuk melakukan itu Anda bisa menggunakan golang builtin baru , atau menginisialisasi pointer ke sana:

config := new(Config) // not nil

atau

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

atau

var config *Config // nil

Maka Anda akan dapat memeriksa apakah

if config == nil {
    // then
}
Oleiade
sumber
5
Saya kira var config &Config // nilseharusnya:var config *Config
Tomasz Plonka
var config *Configcrash dengan invalid memory address or nil pointer dereference. Mungkin kita perluvar config Config
kachar
Saya mengerti alasan di balik pilihan ini mungkin bukan milik Anda, tetapi tidak masuk akal bagi saya bahwa "jika! (Config! = Nil)" valid tetapi "jika config == nil" tidak. Keduanya melakukan perbandingan antara struct dan non-struct yang sama.
retorquere
@retorquere keduanya tidak valid, lihat play.golang.org/p/k2EmRcels6 . Apakah itu '! =' Atau '==' tidak ada bedanya; apa yang membuat perbedaan adalah apakah config adalah struct atau pointer ke struct.
stewbasic
Saya pikir ini salah, karena selalu salah: play.golang.org/p/g-MdbEbnyNx
Madeo
61

Selain Oleiade, lihat spesifikasi pada nilai nol :

Ketika memori dialokasikan untuk menyimpan nilai, baik melalui deklarasi atau panggilan make atau baru, dan tidak ada inisialisasi eksplisit disediakan, memori diberikan inisialisasi default. Setiap elemen dari nilai tersebut diatur ke nilai nol untuk jenisnya: false untuk boolean, 0 untuk bilangan bulat, "0 untuk pelampung," "untuk string, dan nol untuk pointer, fungsi, antarmuka, irisan, saluran, dan peta. Inisialisasi ini dilakukan secara rekursif, jadi misalnya setiap elemen array array akan memiliki bidangnya nol jika tidak ada nilai yang ditentukan.

Seperti yang Anda lihat, nilbukan nilai nol untuk setiap jenis tetapi hanya untuk pointer, fungsi, antarmuka, irisan, saluran dan peta. Ini adalah alasan mengapa config == nilada kesalahan dan &config == niltidak.

Untuk memeriksa apakah struct Anda belum diinisialisasi, Anda harus memeriksa setiap anggota untuk nilai nol masing-masing (mis host == "". port == 0, Dll.) Atau memiliki bidang pribadi yang ditetapkan oleh metode inisialisasi internal. Contoh:

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
sumber
4
Lebih jauh ke atas, itulah sebabnya time.Timememiliki IsZero()metode. Namun Anda juga bisa melakukan var t1 time.Time; if t1 == time.Time{}dan Anda juga bisa melakukan if config == Config{}untuk memeriksa semua bidang untuk Anda (kesetaraan struct didefinisikan dengan baik di Go). Namun, itu tidak efisien jika Anda memiliki banyak bidang. Dan, mungkin nilai nol adalah nilai yang waras dan dapat digunakan sehingga melewati satu di tidak istimewa.
Dave C
1
Fungsi diinisialisasi akan gagal, jika Config sebagai pointer diakses. Itu bisa diubah menjadifunc (c *Config) Initialized() bool { return !(c == nil) }
Sundar
@Sundar dalam hal ini mungkin nyaman untuk melakukannya dengan cara ini, jadi saya menerapkan perubahan. Namun, biasanya saya tidak akan mengharapkan akhir dari panggilan metode untuk memeriksa apakah itu sendiri adalah nihil, karena ini harus menjadi pekerjaan pemanggil.
nemo
16

Saya telah membuat beberapa kode sampel yang menciptakan variabel baru menggunakan berbagai cara yang dapat saya pikirkan. Sepertinya 3 cara pertama membuat nilai, dan dua yang terakhir membuat referensi.

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

yang keluaran:

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

Anda juga dapat memeriksa suka struct_var == (struct{}). Ini tidak memungkinkan Anda untuk membandingkan dengan nol tetapi memeriksa apakah itu diinisialisasi atau tidak. Hati-hati saat menggunakan metode ini. Jika struct Anda dapat memiliki nilai nol untuk semua bidangnya, Anda tidak akan bersenang-senang.

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

Spesifikasi bahasa menyebutkan perilaku operator perbandingan:

operator pembanding

Dalam perbandingan apa pun, operan pertama harus ditetapkan untuk jenis operan kedua, atau sebaliknya.


Penugasan

Nilai x dapat dialihkan ke variabel tipe T ("x dapat dialihkan ke T") dalam kasus berikut:

  • tipe x identik dengan T.
  • x tipe V dan T memiliki tipe dasar yang identik dan setidaknya salah satu dari V atau T bukan tipe bernama.
  • T adalah tipe antarmuka dan x mengimplementasikan T.
  • x adalah nilai saluran dua arah, T adalah tipe saluran, tipe x V dan T memiliki tipe elemen yang identik, dan setidaknya salah satu dari V atau T bukan tipe yang bernama.
  • x adalah pengidentifikasi yang dideklarasikan nil dan T adalah pointer, fungsi, iris, peta, saluran, atau tipe antarmuka.
  • x adalah konstanta yang tidak diketik yang diwakili oleh nilai tipe T.
supei
sumber
0

Di Go 1.13 dan yang lebih baru, Anda dapat menggunakan Value.IsZerometode yang ditawarkan dalam reflectpaket.

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

Selain tipe dasar, ini juga berfungsi untuk Array, Chan, Func, Interface, Map, Ptr, Slice, UnsafePointer, dan Struct. Lihat ini untuk referensi.

mrpandey
sumber