Перейти к содержимому
Теория · Map · урок 6 из 9

nil-map

Представьте, что у вас есть ярлык «Телефонная книга», а самой книги ещё нет. На вопрос «есть ли там Анна?» ответить можно: нет, там вообще никого. А вот записать в несуществующую книгу новый номер нельзя.

В Go такой ярлык без книги появляется после объявления var m map[string]int. Оно заводит переменную, но саму map не создаёт. Нулевое значение для типа map — nil, и такую переменную называют nil-map. Читать из неё можно без ошибок:

GO
package main

import "fmt"

func main() {
    var m map[string]int
    fmt.Println(m == nil)          // true
    fmt.Println(len(m), m["kiwi"]) // 0 0
}

Давайте разберём вывод:

  1. Переменная m равна nil, поэтому первая строка печатает true.
  2. Во второй строке len возвращает 0.
  3. Чтение по ключу "kiwi" возвращает нулевое значение 0.

Ни одна строка не вызывает ошибку.

Чтение из nil-map

При чтении nil-map ведёт себя как пустая map:

  • функция len возвращает 0;
  • любой ключ читается как нулевое значение, а comma ok даёт false;
  • цикл range по ней не выполняется ни разу;
  • delete ничего не делает;
  • вызов fmt.Println(m) печатает map[], как для пустой map.

Здесь картинка с ярлыком неточна. В жизни на вопрос о книге, которой нет, честнее ответить «книги нет». Go отвечает иначе: так, будто книга есть и в ней пусто. В программе nil-map от пустой map отличает сравнение m == nil. Go разрешает сравнивать map только с nil: две map, как и два среза, оператором == не сравнить, и такой код не скомпилируется.

Поэтому функция, которой нечего вернуть, может вернуть nil-map. Вызывающий код прочитает из результата нули и пройдёт по нему пустым циклом без отдельной проверки на nil.

Запись в nil-map

Запись в nil-map вызывает панику — ошибку во время выполнения, которая останавливает программу:

GO
var m map[string]int
fmt.Println(len(m))
m["a"] = 1 // ✗
fmt.Println(m)
TEXT
panic: assignment to entry in nil map

Давайте разберём, что здесь происходит:

  1. fmt.Println(len(m)) печатает 0.
  2. На строке m["a"] = 1 программа останавливается: записать в nil-map некуда, Go ещё не выделил место для пар.
  3. Последний fmt.Println не выполняется.

Компилятор эту ошибку не ловит: сборка проходит, а паника случается при запуске.

Этот вопрос часто задают на собеседовании: «Что будет при записи в nil-map?» Ответ — паника assignment to entry in nil map. Часто уточняют, что nil-срез ведёт себя иначе:

  • к nil-срезу можно добавлять элементы через append;
  • в nil-map писать нельзя.

Два способа объявить map

Писать можно только в созданную map. Поэтому способ объявления зависит от того, будут ли в map писать.

Сразу созданная map

Объявление m := make(map[string]int) или m := map[string]int{} создаёт пустую map, и такая map уже не равна nil. Писать в неё можно с первой строки. Отсюда правило: если в map будут писать, создавайте её сразу через make или литерал.

Объявление через var

Объявление var m map[string]int даёт nil-map. Оно годится, когда переменной позже присваивают уже созданную map, например результат функции. Ловушка: запись до такого присваивания вызывает панику.

Частая ошибка

map для результата функции объявили через var, а создать забыли:

GO
func countLetters(s string) map[rune]int {
    var counts map[rune]int // ✗
    for _, r := range s {
        counts[r]++
    }
    return counts
}

Для пустой строки функция работает, а для любой другой вызывает панику assignment to entry in nil map на первой же букве. Объявление через var не ошибка само по себе: ошибка в том, что map так и осталась nil. Объявите переменную так: counts := make(map[rune]int).

Главное

  • Нулевое значение map — nil: переменная есть, а самой map ещё нет.
  • Из nil-map можно читать: len даёт 0, любой ключ — нулевое значение, range не выполняется.
  • Запись в nil-map вызывает панику assignment to entry in nil map.
  • map, в которую будут писать, создают сразу: make(map[string]int) или map[string]int{}.

Подпишись, чтобы пройти курс

Курс бесплатный. Войди через Telegram и подпишись на канал — откроются все уроки.

Войти другим способом