Слово nil является существительным, что значит ничего или ноль. В программировании на Go nil является нулевым значением. Как помните, переменная integer, объявленная без значения, по умолчанию будет равна 0. Пустая строка будет нулевым значением переменной string и так далее. Указатель, который ни на что не указывает, принимает значение nil. Идентификатор nil также обладает нулевым значением для срезов, карт и интерфейсов.

После изучения данной статьи вы сможете:

Форум Гоферов

Мы работаем над форумом для программистов на Golang. Очень нужны модераторы которые хотят помочь с ответами для новичков и помочь в развитии Go-сообщества.

Go на Форум

Уроки, статьи и Видео

Мы публикуем в паблике ВК и Telegram качественные обучающие материалы для быстрого изучения Go. Подпишитесь на нас в ВК и в Telegram. Поддержите сообщество Go программистов.

Go в ВК Go в Telegram

  • Сделать что-то из ничего;
  • Разобраться с проблемами в nil;
  • Увидеть, как Go усовершенствовал nil.

Содержание статьи

Многие языки программирования также используют концепт nil. Среди его других названий — NULL, null или None. В 2009 году перед релизом Go проектировщик языков программирования Тони Хоар выступил с презентацией под названием «Null References: The Billion Dollar Mistake«. В своей речи Хоар утверждал, что он ответственен за изобретение отсылки null в 1965 году. Он также говорил о том, что указатели в никуда были не лучшей идеей.

На заметку: В 1978 Тони Хоар также впервые описал принцип взаимодействующих последовательных процессов, или «communicating sequential processes», CSP. Его идеи лежат в основе конкурентности Go.

В Go nil является более дружелюбен и менее распространен, нежели в других языках программирования, однако и здесь нужно быть готовым к некоторым проблемам. Nil можно использовать не только по его прямому назначению, о чем говорит Франчес Кампой в своей презентации на GopherCon 2016.

Представьте созвездие, где каждая звезда указывает на ближайшую к ней соседнюю звезду. После вычислений, каждая звезда будет куда-то указывать, и нахождение ближайшей звезды становится быстрым разыменованием указателя.

Однако, пока вычисления не закончены, куда должны указывать указатели? Это тот случай, когда пригодится nil. Nil может значить ближайшую звезду, пока реальная звезда не будет найдена.

В какой еще ситуации указатель в никуда может быть полезен?

Вызывает ли nil сбои в Golang?

Если указатель никуда не указывает, попытка разыменования указателя не сработает, что показано в Листинге 1. Разыменование указателя nil приведет к сбою программы. Обычно пользователям такое совсем не нравится.

Я называю это моей ошибкой в миллиард долларов.

Тони Хоар

Избежать сбоя несложно. Это вопрос защиты от разыменования указателя nil с оператором if, что показано в следующем листинге.

По правде говоря, сбой в программе может стать следствием многих причин, не только разыменования указателя nil. К примеру, деление на ноль также приведет к сбою, и решение проблемы будет схожим. Даже так, подумайте обо всех программах, написанных в течение последних 50 лет, количество случайных разыменований указателя nil должен быть весьма значительным.

Существование nil обременяет программиста необходимостью принятия дополнительных решений. Должен ли код проверять наличие nil, и если да, то где? Что код должен делать, если какое-то значение равно nil? После всего вышесказанного, так уж ли плох nil?

Вовсе не обязательно постоянно пытаться избегать nil. По правде говоря, в некоторых случаях nil весьма полезен. В дополнение ко всему указатели nil в Go не так популярны, как указатели null в некоторых других языках, и есть способы избежать их использования в случае нужды.

Вопрос для проверки:

Каким будет нулевое значения типа *string?

Защита методов в Golang

Методы регулярно получают указатели на структуры, что значит приемник может быть nil, как показано в примере ниже. Происходит ли разыменование указателя явно (*p) или неявно через получение доступа к полю структуры (p.age), значение nil вызовет сбой.

Скорее всего, сбой вызван после выполнения строки p.age++. Удалите данную строку, тогда программа запустится.

На заметку: Сравните это с аналогичной программой в Java, где приемник null приведет к сбою программы сразу после вызова метода.

Go вызывает методы даже в том случае, если у приемника значение nil. Приемник nil ведет себя так же, как и параметр nil. Это значит, что методы могут защищать от значений nil, как показано в следующем примере.

Вместо проверки на наличие nil перед вызовом метода birthday предыдущий листинг защищает от приемников nil внутри метода.

На заметку: В Objective-C автоматический запуск метода для nil не приводит к сбою, но при вызове метода будет возвращаться нулевое значение.

С тем, как управлять nil в Go, разобрались. Методы могут возвращать нулевые значения, возвращать ошибки или приводить к сбою.

Вопрос для проверки:

Что делает доступ к полю (p.age), если p является nil?

Значения функций nil в Golang

Когда переменная объявляется как тип функции, ее значение по умолчанию будет nil. В следующем листинге у fn тип функции, но нет присваивания к какой-то конкретной функции.

Если бы предыдущий код вызывал fn(1, 2), программа привела бы к сбою с разыменованием указателя nil, потому что нет функции, присвоенной fn.

Можно проверить, является ли значение функции nil, и предоставить поведение по умолчанию. В следующем листинге sort.Slice используется для сортировки среза строк с функцией первого класса less. Если nil передается аргументу less он приводится по умолчанию к функции для сортировки в алфавитном порядке.

Задание для проверки:

Напишите одну строку кода для сортировки food от самой короткой до самой длинной строки Листинга 6.

Срезы nil в Golang

Срез, объявленный без композитного литерала или встроенной функции make, будет со значением nil. К счастью, ключевое слово range, а также встроенные функции len и append будут работать со срезами nil, как показано в следующем примере.

Пустой срез и срез nil не эквивалентны, но зачастую они заменяют друг друга. Следующий пример передает nil для функции, что принимает срез, пропуская шаг создания пустого среза.

Если вы пишите функцию, что принимает срез, убедитесь, что срез nil ведет себя так же, как и пустой срез.

Вопрос для проверки: 

Какие действия можно безопасно осуществлять над срезом nil?

Карты nil в Golang

Как и срезы, карты объявленные без композитного литерала или встроенной функции make, обладают значением nil. Карты можно прочитать даже когда у них значение nil, что показано в следующем листинге, хотя запись в карту nil вызовет сбой.

Если функция только читает из карты, можно передавать nil функции вместо создания пустой карты.

Вопрос для проверки:

Какие действия на картой nil приведут к сбою?

Интерфейсы nil в Go

Когда объявляется переменная типа интерфейса без присваивания, нулевым значением является nil. Следующий листинг показывает, что тип интерфейса и значение являются nil, и переменная считается равной nil.

Когда переменной с типом интерфейса присваивается значение, интерфейс внутренне указывает на тип и значение данной переменной. Это приводит к довольно удивительному поведению значения nil, что не считается равным nil. Оба, тип интерфейса и значение должны быть типа nil для того чтобы переменная была равна nil, что показано в следующем примере:

Специальный символ %#v  нужен для того, чтобы увидеть тип и значение, а также отображения того, что переменная содержит (*int)(nil), а не просто <nil>, как показано в Листинге 12.

Во избежание неприятных сюрпризов при сравнении интерфейсов с nil лучше явно использовать идентификатор nil вместо указания на переменную, что содержит nil.

Вопрос для проверки:

Каким будет значение s при объявлении как var s fmt.Stringer?

Альтернатива nil в Golang

Если значения нет, использование nil может показаться заманчивым. К примеру, указатель на целое число (*int) может представлять как ноль, так и nil. Указатели нужны для указания, поэтому использование указателя просто для предоставления значение nil не является лучшим вариантом.

Вместо использования указателя можно объявить небольшую структуру с несколькими методами. Это требует большего количества кода, но не запрашивает указатель или nil, как показано в следующем листинге.

Вопрос для проверки:

В чем преимущество подхода, используемого в Листинге 13?

Заключение

  • Разыменование указателей nil приведет к сбою программы;
  • Методы могут защитить от получения значений nil;
  • Поведение по умолчанию может быть предоставлено функциям, передаваемых в виде аргументов;
  • Срез nil часто можно заменить пустым срезом;
  • Карту nil можно читать, но записывать в нее нельзя;
  • Если интерфейс выглядит так, будто это nil, убедитесь, что оба тип и значение являются nil;
  • nil — не единственный способ представления пустого значения.

Итоговое задание для проверки:

Рыцарь встал на пути Артура. Герой безоружен, он представлен значением nil для leftHand *item. Имплементируйте структуру character с методами вроде pickup(i *item) и give(to *character). Потом используйте выученное в данном уроке для написания скрипта, в котором Артур достает объект и передает его рыцарю. Каждое действие должно отображаться с соответствующим описанием.