Что такое nil в golang
Значение nil в Golang
После изучения данной статьи вы сможете:
Рекомендуем вам супер TELEGRAM канал по Golang где собраны все материалы для качественного изучения языка. Удивите всех своими знаниями на собеседовании! 😎
Мы публикуем в паблике ВК и Telegram качественные обучающие материалы для быстрого изучения Go. Подпишитесь на нас в ВК и в Telegram. Поддержите сообщество Go программистов.
Содержание статьи
На заметку: В 1978 Тони Хоар также впервые описал принцип взаимодействующих последовательных процессов, или «communicating sequential processes», CSP. Его идеи лежат в основе конкурентности Go.
В Go nil является более дружелюбен и менее распространен, нежели в других языках программирования, однако и здесь нужно быть готовым к некоторым проблемам. Nil можно использовать не только по его прямому назначению, о чем говорит Франчес Кампой в своей презентации на GopherCon 2016.
Представьте созвездие, где каждая звезда указывает на ближайшую к ней соседнюю звезду. После вычислений, каждая звезда будет куда-то указывать, и нахождение ближайшей звезды становится быстрым разыменованием указателя.
Однако, пока вычисления не закончены, куда должны указывать указатели? Это тот случай, когда пригодится nil. Nil может значить ближайшую звезду, пока реальная звезда не будет найдена.
В какой еще ситуации указатель в никуда может быть полезен?
Вызывает ли nil сбои в Golang?
Если указатель никуда не указывает, попытка разыменования указателя не сработает, что показано в Листинге 1. Разыменование указателя nil приведет к сбою программы. Обычно пользователям такое совсем не нравится.
Я называю это моей ошибкой в миллиард долларов.
Nil не всегда nil
«Что? Что вообще здесь написано?» спросите вы. Сейчас все разложу.
Когда начинал изучать язык — не думал что зайду в этот узкий случай. Это также не рационально как и изменять итерируемую коллекцию.
На примере:
Что представляет собой интерфейс
Переходим в файл пакета go runtime/runtime2.go и видим:
Интерфейс хранит в себе тип интерфейса и тип самого значение.
Значение любого интерфейса, не только error, является nil в случае когда И значение И тип являются nil.
Функция Foo возвращает nil типа *os.PathError, результат мы сравниваем с nil типа nil, откуда и следует их неравенство.
Возможно об этом многие знали, но мало кто думает как попасть в это на практике.
Мой пример
Response всегда имеет результат или ошибку.
При наличии ошибки — отправляем ее куда необходимо через сервис нотификаций.
Внутри сервиса вызывается метод Error, а так как наше значение nil — получаем панику.
Что делать?
Возвращать интерфейс строго типа интерфейса.
В случае ошибки — типа ошибки.
Этот прием тоже, к моему удивлению, не работает.
Получается так, что при присвоении значение в переменную err мы также передаем ей исходную информацию о типе, который не есть nil.
Да, такой прием работает.
Но будем честны, мы не можем позволить себе проверить все типы ошибок которые мы будем передавать.
Это могут быть все ошибки из драйвера базы данных, все наши внутренние ошибки и прочий мусор.
Какой наиболее рациональный вариант я вижу:
Вначале у нас объявленa переменная типа error, как оказывается со значением и типом nil.
И прежде чем передать наш тип и значение этой переменной — проверим наш тип и его значение на nil.
Это позволит нам не упасть с паникой.
Напоследок
Можно пойти еще дальше и реализовать «опциональную» ошибку у типа Response, OptionalError или ErrorOrNil, вроде такого:
На заметку
В заметках Go wiki код ревью заметка в топике об интерфейса: Instead return a concrete type and let the consumer mock the producer implementation.
Отмечу, что приведенные мной выше пляски не про это.
Мои заметки позволяют не упасть с паникой когда вы знаете, что хотите вернуть интерефйс, а в случае ошибок — вы всегда хотите возвращать интерфейс.
Но если вы можете себе позволить вернуть определенный тип — возвращайте его.
Go: Как использовать nil-значения без использования ссылочных типов
Создано на базе изображений gopherize.me
Довольно часто из Go кода нам приходится работать с различными HTTP API или самим выполнять роль HTTP сервиса.
Один из частых случаев: получаем данные в виде структуры из базы данных, отправляем структуру внешнему API, в ответ получаем другую структуру, как-то её преобразуем и сохраняем в базу.
Другими словами: такая обработка не требует множества отдельных операций со структурами запроса и ответа.
Для API нормальна ситуация, когда в структурах запроса и ответа есть поля, которые могут быть nil и могут принимать какие-то не-nil значения. Такие структуры выглядят обычно так
И, так как это ссылочный тип, то Go компилятор делает escape анализ и может перенести данную переменную в хип. В случае частого создания таких переменных — мы получаем лишнюю нагрузку на GC и даже можем получить «утечку памяти», если GC не успевает освободить всю использованную память.
Что можно сделать в такой ситуации:
Для начала, давайте сравним разницу между работой со ссылочными типами и передачей переменных «по значению»
Все бенчмарки воспроизводимы и размещены здесь.
В Go коде мы обычно используем такие структуры со ссылочными типами
Давайте сравним их со структурами с типами, которые передаются по значению
Структура со ссылками обработана с 0 аллокаций, аналогично структуре со значениями.
Здесь мы даже видим, что структура со ссылками быстрее по времени обработки.
Небольшое замечание: в данном бенчмарке мы видим две механики Go, которые объясняют эти (для кого-то неожиданные) результаты.
Первое — инлайнинг функций. Это механизм, когда вызов функции заменяется на её тело. В данном случае для структур сработал этот механизм и это позволило избежать переноса структур в хип. Как результат — нет аллокаций. Во всех следующих бенчмарках мы не будем отключать этот механизм, т.к. хотим сравнить работу обычного Go кода.
Второе — копирование структур с типами, которые передаются по значению. На копирование структур тратится дополнительное время, что мы и видим в результатах теста.
Давайте сделаем бенчмарк с вызовом функций и передачей туда структур со ссылками и указателями. Как мы видим, структуры со ссылками обрабатываются всё-ещё быстрее структур со значениями и по-прежнему с нулевым количеством аллокаций.
В сервисах часто используется JSON кодирование и декодирование структур. Давайте сравним результаты кодирования и декодирования структур, используя jsoniter. В данном бенчмарке ситуация меняется. Структуры со значениями быстрее и лучше по использованию памяти на одну операцию, лучше по количеству аллокаций.
Давайте попробуем улучшить результаты кодирования и декодирования, используя easyjson. Почти все результаты для обеих структур лучше, кроме чуть большего использования памяти на одну операцию.
Предварительный вывод: если в вашем коде длинная цепочка обработки полученного значения с итоговым результатом, то иногда будет лучше использовать ссылочные типы. Но если цепочка обработки вашего кода короткая (кодирование/декодирование значения) — то лучше использовать структуры со значениями.
Пойдём дальше. Иногда структуры растут в размере
Сделаем бенчмарк для этих структур. Далее мы видим, что для структуры со значениями обработка как и раньше даёт 0 аллокаций, но увеличилось время обработки (это нормально, т.к. структура стала больше). Так же, структура со ссылками потеряла преимущество: ненулевое количество аллокаций и значительно большее время обработки и использование памяти на одну операцию
Попробуем передать данные структуры через цепочку вызовов функций. Для структур со ссылками ничего не изменилось. Для структур со значениями незначительно выросло время обработки (но всё ещё меньше, чем для структуры со ссылками).
Попробуем сделать кодирование и декодирование. Структура со значениями лучше по всем параметрам
Попробуем улучшить результат, используя easyjson. Структура со значениями лучше во всём. Структура со ссылками обрабатывается лучше, чем в jsoniter.
Итоговый вывод: не делайте оптимизации на первом этапе разработки — лучше предпочесть использовать структуры со значениями, чем структуры со ссылками. И только когда производительность перестала устраивать — пройдите по цепочке обработки и попробуйте переключиться на передачу значений по ссылке в «горячих местах». Предпочтительно использовать кодогенераторы (easyjson и другие), чем обработку в коде — в большинстве случаев получим результаты лучше.
Переключение на структуры со значениями
Переключение выглядит просто — использовать Nullable типы. Пример из библиотеки sql — sql.NullBool, sql.NullString и другие.
Так же, для типа потребуется описать функции кодирования и декодирования
Как результат избавления от ссылочных типов в API — я разработал библиотеку nan, с основными Nullable типами с функциями кодирования и декодирования для JSON, jsoniter, easyjson, gocql.
Удобство использования Nullable типов
И один из последних вопросов, которые можно задать про переключение на Nullable типы — удобно ли их использовать.
Моё личное мнение — удобно, у типов тот же паттерн использования что и у ссылок на переменые.
What does nil mean in golang?
There are many cases using nil in golang. For example:
but we can’t use it like this:
the golang compiler will throw a can’t use nil as type string in assignment error.
Looks like nil can only be used for a pointer of struct and interface. If that is the case, then what does it mean? and when we use it to compare to the other object, how do they compare, in other words, how does golang determine one object is nil?
EDIT:For example, if an interface is nil, its type and value must be nil at the same time. How does golang do this?
8 Answers 8
In Go, nil is the zero value for pointers, interfaces, maps, slices, channels and function types, representing an uninitialized value.
nil in Go is simply the NULL pointer value of other languages.
You can effectively use it in place of any pointer or interface (interfaces are somewhat pointers).
You can use it as an error, because the error type is an interface.
You can’t use it as a string because in Go, a string is a value.
nil is untyped in Go, meaning you can’t do that:
nil is a predeclared identifier in Go that represents zero values for pointers, interfaces, channels, maps, slices and function types. nil being the zero value of pointers and interfaces, uninitialized pointers and interfaces will be nil.
nil is also a value but only difference is- it is empty.
In Javascript for the un-initialized variable will be undefined. In the same way Golang has nil as default value for all the un-initalized data types.
nil in Go means a zero value for pointers, interfaces, maps, slices, and channels. It means the value is uninitialized
nil detection in Go
I see a lot of code in Go to detect nil, like this:
however, I have a struct like this:
and config is an instance of Config, when I do:
there is compile error, saying: cannot convert nil to type Config
6 Answers 6
The compiler is pointing the error to you, you’re comparing a structure instance and nil. They’re not of the same type so it considers it as an invalid comparison and yells at you.
What you want to do here is to compare a pointer to your config instance to nil, which is a valid comparison. To do that you can either use the golang new builtin, or initialize a pointer to it:
Then you’ll be able to check if
In addition to Oleiade, see the spec on zero values:
When memory is allocated to store a value, either through a declaration or a call of make or new, and no explicit initialization is provided, the memory is given a default initialization. Each element of such a value is set to the zero value for its type: false for booleans, 0 for integers, 0.0 for floats, «» for strings, and nil for pointers, functions, interfaces, slices, channels, and maps. This initialization is done recursively, so for instance each element of an array of structs will have its fields zeroed if no value is specified.
As you can see, nil is not the zero value for every type but only for pointers, functions, interfaces, slices, channels and maps. This is the reason why config == nil is an error and &config == nil is not.