ноль обнаружения в го


165

Я вижу много кода в Go, чтобы обнаружить ноль, например так:

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

Тем не менее, у меня есть такая структура:

type Config struct {
    host string  
    port float64
}

и config это экземпляр Config, когда я делаю:

if config == nil {
}

есть ошибка компиляции, говорящая: не может преобразовать ноль в тип Config


3
Я не понимаю, почему порт имеет тип float64?
аламин

2
Так не должно быть. API JSON Go импортирует любое число из JSON в float64, мне нужно преобразовать float64 в int.
Цянь Чен

Ответы:


179

Компилятор указывает на ошибку, вы сравниваете экземпляр структуры и nil. Они не одного типа, поэтому он считает это недопустимым сравнением и кричит на вас.

Здесь вы хотите сравнить указатель на ваш экземпляр конфигурации с nil, что является допустимым сравнением. Для этого вы можете использовать новый встроенный golang или инициализировать указатель на него:

config := new(Config) // not nil

или

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

или

var config *Config // nil

Тогда вы сможете проверить, если

if config == nil {
    // then
}

5
Наверное, var config &Config // nilдолжно быть:var config *Config
Томаш Плонька

var config *Configвылетает с invalid memory address or nil pointer dereference. Может быть, нам нужноvar config Config
Качар

Я понимаю, что причины этого выбора могут быть не вашими, но для меня нет смысла, что «if! (Config! = Nil)» является действительным, а «if config == nil» - нет. Оба делают сравнение между одной и той же структурой и неструктурой.
retorquere

@retorquere они оба недействительны, см. play.golang.org/p/k2EmRcels6 . Будь то «! =» Или «==» не имеет значения; что имеет значение, так это то, является ли config структурой или указателем на struct.
тушеное мясо

Я думаю, что это неправильно, поскольку это всегда ложь: play.golang.org/p/g-MdbEbnyNx
Мадео

61

В дополнение к Oleiade, смотрите спецификацию на нулевые значения :

Когда память выделяется для хранения значения либо через объявление, либо через вызов make или new, и не предоставляется явная инициализация, памяти назначается инициализация по умолчанию. Каждому элементу такого значения присваивается нулевое значение для его типа: false для логических значений, 0 для целых чисел, 0.0 для чисел с плавающей запятой, "" для строк и ноль для указателей, функций, интерфейсов, секций, каналов и карт. Эта инициализация выполняется рекурсивно, поэтому, например, каждый элемент массива структур будет обнулять свои поля, если не указано значение.

Как видите, nilэто не нулевое значение для каждого типа, а только для указателей, функций, интерфейсов, срезов, каналов и карт. Это причина, почему config == nilошибка и &config == nilнет.

Чтобы проверить, не является ли ваша структура неинициализированной, вы должны проверить каждый элемент на предмет его соответствующего нулевого значения (например host == "", port == 0и т. Д.) Или иметь закрытое поле, которое устанавливается внутренним методом инициализации. Пример:

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 }

4
В дополнение к вышесказанному, поэтому time.Timeесть IsZero()метод. Однако вы также можете сделать, var t1 time.Time; if t1 == time.Time{}и вы можете также if config == Config{}проверить все поля для вас (структурное равенство хорошо определено в Go). Однако это неэффективно, если у вас много полей. И, возможно, нулевое значение является нормальным и пригодным для использования значением, поэтому его передача не является особенной.
Дейв С

1
Функция Initialized потерпит неудачу, если к Config как к указателю обращаются. Это может быть изменено наfunc (c *Config) Initialized() bool { return !(c == nil) }
Сундар

@Sundar в этом случае может быть удобно сделать это таким образом, поэтому я применил изменения. Однако обычно я не ожидал бы, что принимающая сторона вызова метода проверит, является ли он нулем, поскольку это должно быть делом вызывающей стороны.
Немо

16

Я создал пример кода, который создает новые переменные, используя различные способы, которые я могу придумать. Похоже, что первые 3 способа создания значений, а последние два создания ссылок.

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

какие выводы:

false
false
false
false
false
{ 0} { 0} { 0} &{ 0} &{ 0}

6

Вы также можете проверить, как struct_var == (struct{}). Это не позволяет сравнивать с nil, но проверяет, инициализирован он или нет. Будьте осторожны при использовании этого метода. Если ваша структура может иметь нулевые значения для всех своих полей, у вас не будет отличного времени.

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


3

Спецификация языка описывает поведение операторов сравнения:

операторы сравнения

В любом сравнении первый операнд должен быть назначен типу второго операнда или наоборот.


Переуступка

Значение x присваивается переменной типа T («x присваивается T») в любом из следующих случаев:

  • тип х идентичен т.
  • Типы X и V имеют идентичные базовые типы, и по крайней мере один из V или T не является именованным типом.
  • T является типом интерфейса, а x реализует T.
  • x - двунаправленное значение канала, T - тип канала, x-типы V и T имеют идентичные типы элементов, и, по крайней мере, один из V или T не является именованным типом.
  • x - это предварительно объявленный идентификатор nil, а T - указатель, функция, срез, карта, канал или тип интерфейса.
  • x - нетипизированная константа, представимая значением типа T.

1

В Go 1.13 и более поздних версиях вы можете использовать Value.IsZeroметод, предложенный в reflectпакете.

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

Помимо основных типов, он также работает для Array, Chan, Func, Interface, Map, Ptr, Slice, UnsafePointer и Struct. Смотрите это для справки.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.