Проектируя API на Go, как определить, примет ли параметр значение nil?
Значение nil можно присвоить только типам, у которых nil является допустимым нулевым значением: указателям, функциям, срезам, map, каналам и интерфейсам. Значениям структур, массивов, числовых, строковых и логических типов nil присвоить нельзя.
Go использует единое значение nil для обозначения отсутствующего значения в типах, которые ссылаются на данные или поведение. Это позволяет отличать «объект не задан» от обычного нулевого значения, например нуля числа или пустой строки.
При этом язык не распространяет nil на все типы: для значимых типов используются их собственные нулевые значения. Такое разделение делает типы предсказуемыми и позволяет компилятору выявлять ошибочные присваивания.
Если API принимает значение типа int, передача nil невозможна: у числа всегда есть конкретное значение, включая ноль. Для optional-семантики применяют указатель, интерфейс или другой явно выбранный тип, способный содержать отсутствие значения.
Важно учитывать, что интерфейс может быть не равен nil, даже если внутри него находится nil-указатель. Интерфейсное значение состоит из динамического типа и динамического значения; оно равно nil только когда оба компонента отсутствуют.
Проверка сводится к нулевому значению типа:
bool и других значимых типов.У каждого допустимого типа есть собственное поведение нулевого значения. Например, чтение из nil-map безопасно, но запись вызывает панику; вызов nil-функции вызывает панику; nil-срез можно передавать и расширять через append.
Для интерфейса нужно различать пустой интерфейс и интерфейс с типизированным nil. Если в интерфейс присвоен nil-указатель, интерфейс хранит динамический тип указателя и потому сам не равен nil.
Сервису нужно обозначить необязательный идентификатор пользователя. Параметр типа int не подходит: значение 0 придётся искусственно трактовать как отсутствие, хотя ноль может быть допустимым идентификатором.
Можно использовать строку с соглашением о пустом значении, но это смешивает данные и служебную семантику. Можно использовать отдельную структуру с полем значения и признаком присутствия, что явно, но многословно.
Практичный вариант для небольшого API — указатель на число: nil означает отсутствие идентификатора, а указатель на ноль — явно переданный ноль. Для публичных доменных моделей структура с отдельным признаком часто лучше документирует состояние и не требует работы с указателями.
1. Вопрос: Чем nil-срез отличается от пустого, если оба можно передать в функцию?
Nil-срез не имеет базового массива и равен nil. Пустой срез обычно имеет длину и ёмкость ноль, но может ссылаться на выделенное или совместно используемое хранилище; он не равен nil. Оба безопасно обходятся и могут расширяться через append, но различие видно при сравнении с nil, сериализации и некоторых API.
2. Вопрос: Почему интерфейс с nil-указателем не равен nil?
Интерфейс хранит пару: динамический тип и динамическое значение. При присваивании nil-указателя интерфейсу динамический тип становится известен, поэтому интерфейс уже не пуст; nil отсутствует только в компоненте значения. Полностью nil-интерфейс не содержит ни типа, ни значения.
3. Вопрос: Почему вызов метода через nil-указатель иногда допустим, а иногда приводит к панике?
Сам факт вызова метода не гарантирует панику: метод может обработать nil-получатель и не обращаться к его полям. Паника возникает при разыменовании nil-указателя или обращении к полю через него; результат зависит от тела метода, а не только от типа получателя.