Интерфейсы в Go
Теория: Nil в интерфейсах
В Go значение nil обозначает отсутствие данных. Оно встречается у указателей, срезов, мап, каналов, функций и интерфейсов. Но именно с интерфейсами nil ведёт себя необычно.
Интерфейс в Go устроен как пара: тип и значение. Он считается пустым только тогда, когда отсутствуют оба. Если задан хотя бы один компонент — интерфейс уже не nil. На практике это создаёт нетривиальные ситуации при сравнении.
Частый случай — в интерфейс передаётся nil-указатель. Значения нет, но тип остаётся. Визуально всё выглядит как nil, но сравнение == nil возвращает false. И это вполне корректно с точки зрения Go.
Такое поведение может сломать логику. Программа может начать обрабатывать ошибку, которой не было, или — наоборот — проигнорировать настоящую. Компилятор и рантайм молчат. Ошибка становится видна только при выполнении, и то не сразу.
Особенно часто эта ситуация возникает с типом error. Посмотрим на пример:
Кажется, что getError() возвращает nil, но:
Интерфейс err не пуст. Значения в нём нет, но тип *MyError сохранён. Такая ошибка даже получила собственное имя — interface value is not nil.
Go сравнивает и тип, и значение. Если задан хотя бы один из них — результат == nil будет ложным. Проверка не сработает, а код пойдёт по неверной ветке.
Поведение касается всех интерфейсов. Например:
Хотя p — nil, переменная v уже содержит тип *int. Интерфейс непустой. Сравнение v == nil даёт false, как и в случае с error.
Чтобы такое не застало врасплох, важно помнить: интерфейс считается пустым только если в нём нет ни типа, ни значения. Всё остальное — уже не nil.
Как быть
Если функция должна вернуть пустой интерфейс — возвращай nil, а не nil-указатель. Это касается и ошибок. Простой return nil безопаснее, чем return (*MyError)(nil).
Если всё же приходится возвращать nil-указатель заранее известного типа, нужно понимать: интерфейс уже будет непустым, и проверка == nil не сработает. Такой случай должен обрабатываться отдельно.
При необходимости можно проверить содержимое интерфейса через reflect:
Во время отладки помогают fmt.Printf("%#v\n", err) и reflect.TypeOf(err) — они показывают, есть ли внутри тип и значение.
Но лучшее решение — писать код так, чтобы такие проверки вообще не понадобились. Если значение отсутствует — возвращай nil. Если оно есть — пусть будет полноценным. nil-указатели внутри интерфейсов — частый источник багов, которых можно избежать одной строчкой.


