Slice - один из наиболее распространенных типов данных, используемых в Golang. Это позволяет нам содержать непрерывные данные любого определенного типа, которые мы можем добавлять или перебирать.
var foo []string foo = append(foo, "a") foo = append(foo, "b") foo = append(foo, "c") for i, v := range { fmt.Println(i, v) } // Output 0 a 1 b 2 c
Также стоит отметить, что инициализированный Slice имеет скрытый массив, что означает, что он разделяет хранилище с массивом. Помимо length,
Slice также имеет capacity
, который является длиной базового массива. Добавление нового значения за пределы емкости приведет к выделению нового массива, который соответствует элементам Slice.
Нулевое значение
В Golang, когда переменная объявляется без значения инициализации, ее значение будет установлено равным нулевому значению типа. Нулевое значение Slice равно nil, поэтому в нашем примере выше, когда мы объявляем var foo []string
, значение foo
на самом деле nil
не является пустым фрагментом строки []
. Пустой срез может быть сгенерирован с помощью коротких объявлений переменных, например. foo := []string{}
или make
функция.
Почему это важно?
var nilSlice []string emptySlice := make([]string, 5) fmt.Println(nilSlice) // Output: [] fmt.Println(len(nilSlice), cap(nilSlice)) // Output: 0 0 fmt.Println(nilSlice == nil) // Output: true fmt.Println(emptySlice) // Output: [] fmt.Println(len(emptySlice), cap(emptySlice)) // Output: 0 0 fmt.Println(emptySlice == nil) // Output: false
Приведенный выше пример демонстрирует, как Nil Slice может обмануть наш глаз. Результат fmt.Println
-ing нулевого фрагмента будет []
точно таким же, как у нас для пустого фрагмента. Он также имеет ту же длину и емкость, что и пустой слайс.
В большинстве случаев нам не нужно относиться к этим двоим по-разному. Мы все еще можем добавлять и повторять нулевой фрагмент, даже если его начальное значение равно нулю. Однако есть и другие случаи, в которых могут возникнуть проблемы.
type Res struct { Data []string } var nilSlice []string emptySlice := make([]string, 5) res, _ := json.Marshal(Res{Data: nilSlice}) res2, _ := json.Marshal(Res{Data: emptySlice}) fmt.Println(string(res)) // Output: {"Data":null} fmt.Println(string(res2)) // Output: {"Data":[]}
Golang encoding/json
кодирует Nil Slice в null
, что может быть неприемлемым, если наш контракт API определяет Data
как ненулевой массив строк.
var nilSlice []string emptySlice := make([]string, 5) fmt.Println(reflect.DeepEqual(nilSlice, emptySlice)) // Output: false fmt.Printf("Got: %+v, Want: %+v\n", nilSlice, emptySlice) //Output: Got: [], Want: []
Другая проблема заключается в том, что мы сравниваем эти два, используя reflect.DeepEqual
, который обычно используется под капотом в библиотеке утверждений. Сравнение будет ложным, что приведет к сбою утверждения, но сообщение с утверждением дает нам то же []
, которое не дает нам спать.