Объясните механизм: почему nil нельзя использовать в коротком объявлении без контекста типа, но можно присвоить указателю, срезу или интерфейсу?
nil — это специальное нетипизированное нулевое значение без собственного типа по умолчанию. Компилятор может использовать его только там, где ожидаемый тип однозначно задаёт допустимый вид нулевого значения: например, для указателя, среза, map, канала, функции или интерфейса. Поэтому объявление переменной с одним nil без контекста типа невозможно.
В Go нулевые значения позволяют безопасно объявлять переменные без обязательной инициализации. Для ссылочных сущностей нулевым состоянием является nil, но такие сущности имеют разные типы и разную семантику: nil-срез можно читать и расширять, nil-map можно читать, но нельзя изменять.
Чтобы сохранить строгую типизацию, Go не приписывает nil произвольный тип автоматически. Иначе одно и то же выражение могло бы неявно означать указатель, срез, канал или другой ссылочный тип.
Короткое объявление использует тип выражения справа для вывода типа новой переменной. У nil нет конкретного типа, поэтому компилятор не может определить, какую именно переменную нужно создать.
При явном контексте тип известен, но остаются практические различия между видами nil: обращение к nil-указателю может привести к панике, отправка в nil-канал блокируется навсегда, а запись в nil-map немедленно вызывает панику.
Контекстом типа служат явная декларация, тип параметра функции, тип результата или приведение. В каждом случае nil получает смысл нулевого значения конкретного типа.
В p значение nil имеет тип указателя на User, в s — тип среза []int, а в x — тип интерфейса any. В последнем случае интерфейсное значение само не содержит динамического типа и значения.
Явное приведение также создаёт необходимый контекст: (*User)(nil) — типизированный nil-указатель. Для функции аналогично используется объявленный тип результата: функция, возвращающая указатель, может вернуть просто nil.
Важно не путать нетипизированное значение с нулевым значением конкретного типа. Выражение nil == nil также некорректно, поскольку ни один операнд не задаёт тип сравнения; сравнение допустимо после помещения nil в контекст конкретного nil-допустимого типа.
В конфигурации сервиса разработчик пишет короткое объявление для необязательного значения и пытается начать с nil. Такой код не компилируется, потому что из одного nil нельзя вывести тип.
Вариант с явной декларацией (var value *Config) прозрачен и задаёт нужное нулевое состояние. Вариант с немедленным приведением (value := (*Config)(nil)) короче, но хуже читается и уместен главным образом в выражениях или тестах. Если значение должно быть изменяемым контейнером, нужно отдельно выбрать срез или map: nil-срез допускает append, тогда как запись в nil-map требует предварительной инициализации.
Практически выбирают явный тип в объявлении, когда nil является значимым состоянием API. Это делает контракт видимым и снижает риск ошибочно выбрать контейнер с неподходящей семантикой.
nil в переменную типа interface{}?Да. При присваивании nil интерфейсу интерфейсное значение становится полностью нулевым: у него нет ни динамического типа, ни динамического значения. Это отличается от помещения в интерфейс типизированного nil-указателя, где динамический тип уже присутствует.
nil можно вернуть из функции без явного приведения?Тип результата функции является контекстом для выражения return. Если функция объявлена с результатом-указателем, срезом, map, каналом, функцией или интерфейсом, компилятор знает, какое нулевое значение требуется. Если результат имеет, например, тип int или структуру, nil ему недопустим.
nil без явного типа в обобщённой функции?Не всегда. Для произвольного параметра типа, ограниченного any, nil недопустим, поскольку конкретным типом может оказаться int или структура. Он допустим только при ограничении, гарантирующем, что все типы в множестве типов являются nil-допустимыми, например указателями; тогда ограничение предоставляет необходимый контекст.