Создание сегмента при его объявлении
Мне всегда казалось, что сегменты не создаются при их объявлении, наверное из-за книги по которой я начинал изучать гоу «Head First. Изучаем Go » 2020 ISBN 978-5-4461-1395-8(страница 210)
вот строки оттуда:

Но наткнулся на то, что всё работает и так:
package main
import "fmt"
func main() {
var strArr []string
fmt.Print(strArr)
strArr = append(strArr,"qwerty")
fmt.Print(strArr)
}
результат:
[][qwerty]
...Program finished with exit code 0
Press ENTER to exit console
Получается, что у меня устаревшая информация и книга была старого издания, либо я в чём-то заблуждаюсь. Исходя из приведённою мною примера, при объявлении сегмента, он всё-таки создаётся автоматически, так ли это?
Ответы (2 шт):
Немного поломал голову над тем что такое "сегмент". Я так понял это перевод slice. Вроде бы обычно переводят как "слайс".
Теперь по существу. Вот такой код
package main
import "fmt"
func main() {
var a []string //объявление переменной, пустой слайс
fmt.Println(a)
a = make([]string, 7) //выделили память
fmt.Println(a)
a = append(a, "qwerty") //добавили строку
fmt.Println(a)
}
Выдает вот такой результат
[]
[ ]
[ qwerty]
Т.е. при объявлении переменной она инициализируется пустым значением. Память имеет смысл выделять через make в том случае если есть планы работать именно с этим фрагментом. А append она, если по простому (и не вдаваться в тонкости реализации), берет два слайса, определяет сколько нужно памяти для того чтобы поместить туда данные из обоих и выделяет эту память, а потом копируюет данные.
Т.е. в вашем примере сначала был объявлен пустой слайс, а потом к нему присоединили данные. В общем-то все штатно - так оно и должно работать. А информацию в книге вы возможно неправильно поняли.
Когда переменная объявляется без инициализатора, ей присваивается zero value. Для слайсов это nil. Ваш пример работает по той причине, что встроенные функции len, range и append корректно работают с nil-овым аргументом. len возвращает 0, range возвращает пустой итератор, а append аллоцирует память и размещает в ней переданные параметры функции. Важно отметить, что после того как вы передали в функцию append nil, append уже не вернёт nil, а вернёт слайс с аллоцированной памятью.
Пример: https://go.dev/play/p/c35hkN_vVbY
package main
import "fmt"
func IsNilSlice[T any](slice []T) bool { return slice == nil }
func main() {
var zero []int
fmt.Println(IsNilSlice(zero))
empty := []int{}
fmt.Println(IsNilSlice(empty))
}
Результат:
true
false
UPDATE
Про empty := []int{} и var empty []int; empty = make([]int,0)
В пакете reflect есть (пока что) структура SliceHeader, которая собственно, и есть слайс в рантайме:
type SliceHeader struct {
Data uintptr
Len int
Cap int
}
Она состоит из трёх полей: указатель на данные, длина слайса и размер куска памяти, на который указывает поле Data.
Посмотрим, как устрены пустые слайсы empty := []int{} и var empty []int; empty = make([]int,0):
package main
import (
"fmt"
"reflect"
"unsafe"
)
func SliceHeader[T any](slice []T) reflect.SliceHeader {
hdrp := (*reflect.SliceHeader)(unsafe.Pointer(&slice))
return *hdrp
}
func main() {
s1 := []int{}
hdr1 := SliceHeader(s1)
fmt.Printf("[]int{}\t\t: header: %#v\n", hdr1)
var s2 []int
s2 = make([]int, 0)
hdr2 := SliceHeader(s2)
fmt.Printf("make([]int, 0)\t: header: %#v\n", hdr2)
}
Результат: https://go.dev/play/p/8puJZ_wTFhJ
[]int{} : header: reflect.SliceHeader{Data:0xc0000466e8, Len:0, Cap:0}
make([]int, 0) : header: reflect.SliceHeader{Data:0xc000084ed0, Len:0, Cap:0}
В обоих случаях это какой-то указатель, нулевая длина и нулевая ёмкость. Указатель на участок памяти нулевой длины, конечно, выглядит странно, но может быть так надо для сборщика мусора.
После append память аллоцируется, указатель изменяется:
func main() {
s1 := []int{}
hdr1 := SliceHeader(s1)
fmt.Printf("[]int{}\t\t\t: header: %#v\n", hdr1)
var s2 []int
s2 = make([]int, 0)
hdr2 := SliceHeader(s2)
fmt.Printf("make([]int, 0)\t\t: header: %#v\n", hdr2)
s1 = append(s1, 1)
fmt.Printf("[]int{} appended\t: header: %#v\n", SliceHeader(s1))
s2 = append(s2, 1)
fmt.Printf("make([]int, 0) appended\t: header: %#v\n", SliceHeader(s2))
}
Результат: https://go.dev/play/p/yoq-owhbb00
[]int{} : header: reflect.SliceHeader{Data:0xc000046698, Len:0, Cap:0}
make([]int, 0) : header: reflect.SliceHeader{Data:0xc000084e80, Len:0, Cap:0}
[]int{} appended : header: reflect.SliceHeader{Data:0xc000012078, Len:1, Cap:1}
make([]int, 0) appended : header: reflect.SliceHeader{Data:0xc000012098, Len:1, Cap:1}
Оба варианта "пустого" слайса ведут себя одинаково. Поэтому можно считать, что это варианты инциализации эквивалентны.