nil-map
Представьте, что у вас есть ярлык «Телефонная книга», а самой книги ещё нет. На вопрос «есть ли там Анна?» ответить можно: нет, там вообще никого. А вот записать в несуществующую книгу новый номер нельзя.
В Go такой ярлык без книги появляется после объявления var m map[string]int. Оно заводит переменную, но саму map не создаёт. Нулевое значение для типа map — nil, и такую переменную называют nil-map. Читать из неё можно без ошибок:
package main
import "fmt"
func main() {
var m map[string]int
fmt.Println(m == nil) // true
fmt.Println(len(m), m["kiwi"]) // 0 0
}Давайте разберём вывод:
- Переменная
mравнаnil, поэтому первая строка печатаетtrue. - Во второй строке
lenвозвращает0. - Чтение по ключу
"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 вызывает панику — ошибку во время выполнения, которая останавливает программу:
var m map[string]int
fmt.Println(len(m))
m["a"] = 1 // ✗
fmt.Println(m)panic: assignment to entry in nil mapДавайте разберём, что здесь происходит:
fmt.Println(len(m))печатает0.- На строке
m["a"] = 1программа останавливается: записать в nil-map некуда, Go ещё не выделил место для пар. - Последний
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, а создать забыли:
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 и подпишись на канал — откроются все уроки.