Zero Values в Go: что это такое, зачем нужны и где чаще всего ошибаются

Zero Values в Go: что это такое, зачем нужны и где чаще всего ошибаются
Одна из самых приятных вещей в 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-разработчики
  • 600+ записей собесов с идеальными ответами
  • прокачка Go в игровом формате (как Duolingo)
  • структурированная обновляемая база знаний по Go
  • комьюнити с быстрым фидбеком
  • практика и лекции, которые реально готовят к рынку

990 ₽/месяц

Закрытый IT-клуб ВЕКТОР: сообщество + приложение

Что такое 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 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 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

  1. Безопасность. Переменная никогда не содержит мусор.
  2. Предсказуемость. Ты почти всегда понимаешь, в каком состоянии находится объект после объявления.
  3. Меньше бойлерплейта. Не надо везде городить конструкторы и ручную инициализацию.
  4. Удобное проектирование API. Хороший Go-код часто можно использовать сразу, без лишних действий.
  5. Читаемость. Когда 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
Senior Go developer
Работал в Авито в инфраструктуре
Кодил на Go, Java, Python, JS
200+ собеседований провел лично
Менторю больше 2 лет
У меня большой нетворк: всегда в курсе, как проходит найм в разных компаниях
Нияз
Автор