

Одна из самых приятных вещей в Go, это предсказуемость. Ты объявляешь переменную, ничего ей не присваиваешь, и всё равно получаешь понятное, безопасное значение. Не мусор из памяти, не undefined, не сюрприз в рантайме на пустом месте, а нормальный стартовый state.
Именно это в Go называется zero value.
Тема кажется простой только на первый взгляд. На деле zero values в Go влияют вообще на всё: на поведение переменных, на работу со структурами, на проектирование API, на удобство стандартной библиотеки и даже на то, насколько безопасно будет пользоваться твоим кодом другим разработчикам.
В этой статье разберём:
- что такое zero values в Go
- какие значения по умолчанию получают разные типы
- почему zero value в Golang, это часть философии языка
- какие типы работают сразу после объявления
- чем отличается nil slice от nil map
- когда нужен make, а когда нужен NewXxx
- какие ошибки с zero values чаще всего допускают Go-разработчики
Что такое zero value в Go
Zero value в Go, это значение по умолчанию, которое получает переменная, если ты её объявил, но не инициализировал вручную.
Например:
package main
import "fmt"
func main() {
var i int
var f float64
var s string
var b bool
fmt.Println(i) // 0
fmt.Println(f) // 0
fmt.Println(s) // ""
fmt.Println(b) // false
}
Это важный момент: в Go не бывает неинициализированных переменных. Язык гарантирует, что у каждой переменной сразу будет валидное значение.
Для разных типов это выглядит так:
| Тип | Zero value |
|---|---|
| int | 0 |
| float64 | 0 |
| string | "" |
| bool | false |
| pointer | nil |
| slice | nil |
| map | nil |
| chan | nil |
| func | nil |
| interface | nil |
Если коротко, zero values в Go, это безопасная стартовая точка для любого значения.

Какие zero values в Go реально полезны на практике
Один из сильных паттернов в Golang, это типы, у которых zero value уже рабочий. То есть не нужно вызывать конструктор, не нужно писать New, не нужно делать лишние действия. Просто объявил переменную и пользуешься.
Вот несколько классических примеров из стандартной библиотеки:
- sync.Mutex{}
- bytes.Buffer{}
- strings.Builder{}
- sync.WaitGroup{}
- sync.Once{}
Почему zero values в Go часть дизайна языка
Во многих языках переменная без инициализации, это потенциальная проблема. Где-то получишь мусор из памяти, где-то null, где-то undefined, а где-то всё упадёт позже и в неожиданном месте.
В Go подход другой. Язык специально спроектирован так, чтобы:
- переменная всегда имела определённое значение
- код был проще и чище
- часть типов можно было использовать сразу после объявления
- программист меньше тратил времени на лишнюю инициализацию
Поэтому zero value в Go, это не просто удобная деталь синтаксиса. Это одна из тех вещей, из-за которых код на Go часто выглядит проще и надёжнее.
Пример с sync.Mutex и bytes.Buffer
package main
import (
"bytes"
"fmt"
"sync"
)
func main() {
var mu sync.Mutex
mu.Lock()
mu.Unlock()
var buf bytes.Buffer
buf.WriteString("hello")
fmt.Println(buf.String()) // hello
}
Обрати внимание: здесь нет ни New, ни make.
Это очень важная идея. Если ты проектируешь свой тип, полезно задавать себе вопрос:
А что будет, если кто-то напишет var x MyType и сразу начнёт им пользоваться?
Если ответ нормальный, значит тип спроектирован удачно. Если ответ такой: "ну тут надо не забыть ещё вызвать инициализацию, иначе всё развалится", значит интерфейс у типа уже не очень дружелюбный.
Таблица zero values в Go, которую реально стоит запомнить
Вот компактная шпаргалка по значениям по умолчанию в Go:
| Категория | Пример типа | Zero value |
|---|---|---|
| Числа | int / int64 / float64 | 0 |
| Логический тип | bool | false |
| Строки | string | "" |
| Указатели | *User | nil |
| Слайсы | []int | nil |
| Карты | map[string]int | nil |
| Каналы | chan int | nil |
| Функции | func() | nil |
| Интерфейсы | error / io.Reader | nil |
| Структуры | struct{} | все поля получают свои zero values |
Zero values в структурах Go
Когда ты создаёшь структуру в Go, все её поля автоматически получают zero values своих типов.
Пример:
package main
import "fmt"
type User struct {
Name string
Age int
Active bool
}
func main() {
var u User
fmt.Printf("%+v\n", u)
}
Вывод будет таким:
{Name: Age:0 Active:false}
Это удобно, потому что не нужно заполнять всё вручную, если тебе подходит состояние по умолчанию.
Но тут есть важный нюанс.
Не все поля одинаково безопасны
Если в структуре есть map, slice, chan или что-то ещё, где для реальной работы нужна инициализация, то zero value может быть формально валидным, но практически неудобным или даже опасным.
Пример:
type Cache struct {
items map[string]string
}
Если написать:
var c Cache
c.items["key"] = "value"
получишь panic, потому что map внутри структуры равен nil.
Когда нужен конструктор NewXxx, а когда не нужен
Очень простое правило:
- если zero value структуры рабочий, конструктор не нужен
- если zero value невалиден или опасен, нужен NewXxx
Пример, где конструктор нужен:
package main
type Item struct {
Value string
}
type Cache struct {
items map[string]Item
}
func NewCache() *Cache {
return &Cache{
items: make(map[string]Item),
}
}
Здесь NewCache() нужен, потому что иначе поле items будет nil, и запись в карту вызовет panic.
Пример, где конструктор не нужен
package main
type Counter struct {
count int
}
func (c *Counter) Inc() {
c.count++
}
Здесь всё хорошо. count по умолчанию равен 0, и с ним уже можно работать.
Nil slice vs nil map в Go: одна из самых частых ловушек
Вот здесь много кто ошибается, особенно в начале.
И slice, и map в Go имеют zero value nil. Но ведут себя они по-разному.
Nil slice в Go
nil slice в Go обычно безопасен:
- len(s) вернёт 0
- cap(s) вернёт 0
- append(s, x) сработает
- range по nil slice не упадёт
Пример:
package main
import "fmt"
func main() {
var s []int
fmt.Println(len(s)) // 0
fmt.Println(cap(s)) // 0
s = append(s, 1)
fmt.Println(s) // [1]
}
Nil map в Go
nil map ведёт себя иначе:
- читать из неё можно
- len(m) вернёт 0
- range сработает с нулём итераций
- писать в неё нельзя, будет panic
Пример:
package main
import "fmt"
func main() {
var m map[string]int
fmt.Println(m["key"]) // 0
m["key"] = 1 // panic: assignment to entry in nil map
}
Почему это важно
Потому что код с nil map компилируется нормально. Компилятор тебя не остановит. Ошибка вылезет уже в рантайме.
Именно поэтому разница между nil slice и nil map в Go, это не теоретическая мелочь, а реальная ловушка, из-за которой падают сервисы.
Почему append работает с nil slice, а запись в nil map ломается
Потому что append возвращает новый slice header, и Go сам может выделить память под элементы.
С map так не работает. Карта должна быть явно инициализирована через make, потому что запись требует внутренней структуры хеш-таблицы, а её у nil map просто нет.
Правильный вариант:
m := make(map[string]int)
m["key"] = 1
Что ещё важно помнить про zero values в Go
Если хочешь глубже понимать тему, вот ещё несколько моментов, которые часто всплывают в реальном коде.
1. Nil channel блокирует выполнение
Zero value для канала, это nil.
var ch chan int
Такой канал нельзя использовать для отправки и получения. Операции с ним будут блокироваться.
ch <- 1 // блокировка
<-ch // блокировка
Чтобы канал работал, нужен make:
ch = make(chan int)
2. Nil interface и typed nil, это не одно и то же
Это уже более продвинутый момент, но знать его полезно. Интерфейс в Go считается nil только когда у него нет ни типа, ни значения.
Вот так:
var err error
fmt.Println(err == nil) // true
А вот так уже нет:
var p *MyError = nil
var err error = p
fmt.Println(err == nil) // false
А вот так уже нет:
Почему? Потому что внутри интерфейса уже лежит тип *MyError, даже если само значение nil.
Эта штука часто кусает в обработке ошибок.
3. Nil slice и empty slice, это не всегда одно и то же
Для большинства операций они выглядят похоже:
var s1 []int
s2 := []int{}
Оба вроде пустые. Но технически это разные состояния:
- s1 == nil -> true
- s2 == nil -> false
Особенно это важно при JSON-маршалинге:
- nil slice часто превращается в null
- пустой slice превращается в []
Если ты пишешь API, это уже может быть критично.
Частые ошибки с zero values в Go
Вот набор ошибок, которые повторяются снова и снова.
Запись в nil map
var m map[string]int
m["x"] = 10 // panic
Исправление:
m := make(map[string]int)
m["x"] = 10
Ожидание, что nil channel будет работать
var ch chan int
ch <- 1 // зависание
Исправление:
ch := make(chan int)
ch <- 1
Уверенность, что zero value структуры всегда безопасен
Не всегда. Если внутри map, а ты собираешься туда писать, нужен конструктор или явная инициализация.
Игнорирование разницы между nil slice и empty slice
На собесах это любят спрашивать. В реальном проде это всплывает в JSON, API и тестах.
Как проектировать свои типы в Go правильно
Один из самых полезных принципов в Golang звучит так:
Если можно сделать так, чтобы zero value был полезен, делай именно так.
Почему это хорошо:
- код проще использовать
- меньше шансов забыть инициализацию
- меньше лишних конструкторов
- API выглядит чище и понятнее
Хороший вопрос при проектировании
Когда создаёшь новый тип, спроси себя:
Что произойдёт, если другой разработчик напишет var x MyType и сразу начнёт использовать?
Дальше варианты:
- если всё работает нормально, значит дизайн удачный
- если код падает, требует обязательного Init() или скрытых условий, значит тип неудобный
Практическое правило
Используй NewXxx, если:
- внутри есть map, в которую нужно писать
- внутри есть chan, который должен работать сразу
- у типа есть обязательные зависимости
- zero value логически невалиден
Не используй NewXxx, если:
- zero value уже безопасен
- тип можно использовать сразу
- конструктор не добавляет реальной пользы
Почему zero values, это сильная сторона Go
- Безопасность. Переменная никогда не содержит мусор.
- Предсказуемость. Ты почти всегда понимаешь, в каком состоянии находится объект после объявления.
- Меньше бойлерплейта. Не надо везде городить конструкторы и ручную инициализацию.
- Удобное проектирование API. Хороший Go-код часто можно использовать сразу, без лишних действий.
- Читаемость. Когда zero value рабочий, код становится проще для всех, кто будет его поддерживать.
Что нужно запомнить про zero values в Go
Если совсем сжать тему до главного, получится вот это:
- у каждого типа в Go есть значение по умолчанию
- zero value, это часть дизайна языка, а не случайная фича
- числа получают 0, строки "", bool получает false
- pointer, slice, map, chan, func, interface получают nil
- nil slice обычно безопасен
- nil map читать можно, писать нельзя
- если zero value структуры опасен, нужен NewXxx
- если тип можно сделать рабочим сразу после var x T, это обычно хороший дизайн
Zero values в Go кажутся базовой темой только до тех пор, пока не начинаешь писать реальный код. Потом выясняется, что именно на них завязано очень много вещей: от поведения обычных переменных до архитектуры собственных типов и удобства API.
Если ты пишешь на Go, полезно не просто помнить таблицу значений по умолчанию, а реально понимать философию за этим механизмом.
И главный вопрос, который стоит держать в голове при проектировании своих структур и пакетов:
Что будет, если кто-то создаст мой тип через var x T и сразу начнёт его использовать?
Если ответ хороший, значит ты двигаешься в правильную сторону.
F.A.Q.
Что такое zero values в Go?
Zero value в Go, это значение по умолчанию, которое получает переменная при объявлении без явной инициализации.
Какой zero value у string в Go?
Для string zero value, это пустая строка "".
Какой zero value у map в Go?
Для map zero value, это nil.
Можно ли писать в nil map в Go?
Нет. Чтение из nil map работает, а запись вызывает panic.
Чем nil slice отличается от nil map в Go?
nil slice можно безопасно использовать с append, len и range. В nil map нельзя записывать без make.
Когда нужен конструктор в Go?
Конструктор NewXxx нужен, если zero value типа нерабочий, опасный или требует обязательной инициализации внутренних полей.
Шпаргалки по Zero Values
Сохранить шпаргалку по Zero Values в Golag по ссылке https://t.me/niyaz_golang/675








