Небольшая шпаргалка по работе с nil и неинициализированными типами: что считается нулевым значением, где можно нарваться на панику, а где всё безопасно. Это один из частых блоков вопросов на собеседовании.
Массивы
Массив всегда выделяется и инициализируется нулями — nil он быть не может.
var arr [3]int
fmt.Println(arr) // [0 0 0]
if arr == nil {} // ошибка компиляцииЛитерал инициализирует массив явно:
arr2 := [...]int{1, 2, 3}
fmt.Println(arr2) // [1 2 3]Слайсы
nil-слайс — нет нижележащего массива, len и cap равны 0. Доступ по индексу паникует, но append работает:
var s []int
fmt.Println(s == nil) // true
fmt.Println(len(s), cap(s)) // 0 0
// s[0] = 1 // panic: index out of range
s = append(s, 10)
fmt.Println(s[0]) // 10Пустой non-nil слайс — не nil, но len и cap всё ещё 0:
s := []int{}
fmt.Println(s == nil) // false
fmt.Println(len(s), cap(s)) // 0 0
s = append(s, 20)
fmt.Println(s[0]) // 20Мапы
nil-мапа — чтение возвращает нулевое значение, запись по ключу паникует:
var m map[string]int
fmt.Println(m == nil) // true
fmt.Println(m["foo"]) // 0
// m["foo"] = 1 // panic
fmt.Println(len(m)) // 0Инициализированная мапа (через make или литерал) — чтение и запись работают:
m1 := make(map[string]int)
m1["foo"] = 42
fmt.Println(m1["foo"]) // 42
m2 := map[string]int{}
m2["bar"] = 7
fmt.Println(m2["bar"]) // 7Каналы
nil-канал — отправка и приём блокируются навсегда (deadlock), close паникует:
var ch chan int
ch <- 1 // deadlock
<-ch // deadlock
close(ch) // panicИнициализированный канал — буферизованный блокируется только при полном буфере:
ch1 := make(chan int)
go func() { ch1 <- 100 }()
fmt.Println(<-ch1) // 100
ch2 := make(chan int, 1)
ch2 <- 200
fmt.Println(<-ch2) // 200
close(ch2)
fmt.Println(<-ch2) // 0 — нулевое значениеФункции
nil-функция — переменная-функция равна nil, вызов паникует:
var f func()
fmt.Println(f == nil) // true
f() // panicОбычная функция — после присваивания литерала или существующей функции её можно вызывать:
f = func() { fmt.Println("Hi") }
f() // Hi
g := math.Abs
fmt.Println(g(-3.14)) // 3.14Указатели
nil-указатель — не указывает ни на что, разыменование *p паникует:
var p *int
fmt.Println(p == nil) // true
// fmt.Println(*p) // panicnon-nil указатель — через new или взятие адреса переменной:
x := 5
p = &x
fmt.Println(*p) // 5
q := new(int)
fmt.Println(*q) // 0
*q = 7
fmt.Println(*q) // 7Интерфейсы
nil-интерфейс — оба поля (тип и значение) равны nil, любая операция паникует:
var i interface{}
fmt.Println(i == nil) // true
i.(string) // panicИнтерфейс без значения (typed nil) — тип не nil, а значение — nil. Сам интерфейс уже ≠ nil:
var p *int
i = p
fmt.Println(i == nil) // false
if v, ok := i.(*int); ok {
fmt.Println(v, *v) // 0xc0000..., 0
}Подробнее про эту ловушку — в статье про интерфейсы.
Ошибки
error — это интерфейс, его нулевое значение — nil:
var err error
fmt.Println(err == nil) // true
err = errors.New("oops")
if err != nil {
fmt.Println(err) // oops
}