nil и инициализация типов в Go: шпаргалка

Обновлено 2026-05-26

Небольшая шпаргалка по работе с 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)         // panic

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

Похожие статьи