Какое последствие имеет вызов переменной-функции, которой не присвоено значение?
Вызов переменной-функции со значением nil приводит к панике во время выполнения. Это не ошибка компиляции: переменная функции может существовать, но не ссылаться ни на одну вызываемую функцию.
Панику можно перехватить через recover, однако обычно надёжнее явно определить, допустимо ли отсутствие обработчика, до его вызова.
В Go каждый тип имеет нулевое значение. Для функционального типа таким значением является nil, что позволяет объявлять переменную-функцию без немедленной инициализации.
Такой подход упрощает работу с необязательными callback-функциями, но переносит ответственность за проверку готовности вызова на разработчика. Нулевое значение не обязано быть безопасным для любой операции.
Переменная-функция может попасть в программу из конфигурации, фабрики, структуры зависимостей или набора обработчиков. Если ни один обработчик не назначен, попытка вызова завершится паникой и может прервать выполнение текущего запроса или фоновой задачи.
Особенно опасна ситуация, когда отсутствие функции считается штатным сценарием, но проверка на nil не выполняется. Простое наличие переменной-функции ещё не означает, что её можно вызвать.
Функциональное значение состоит из ссылки на вызываемое поведение и имеет нулевое состояние nil. Проверка handler == nil определяет, присвоена ли переменной конкретная функция; после успешной проверки её можно вызывать.
В примере компиляция успешна, но при выполнении выражения происходит паника из-за вызова nil-функции. Присваивание переменной другой функции делает вызов допустимым, а изменение самой функции внутри вызываемого кода не происходит автоматически.
Есть два распространённых решения. Первое — явно проверять nil и выбирать запасное поведение: пропустить операцию, вернуть ошибку или использовать значение по умолчанию. Второе — при создании объекта назначать безопасную функцию-заглушку без побочных эффектов; это убирает проверки, но может скрыть ошибку конфигурации.
Проверка должна соответствовать контракту. Если обработчик обязателен, лучше обнаружить отсутствие функции при создании объекта и вернуть ошибку, чем допустить отложенную панику в рабочем потоке.
Сервис принимает необязательный callback для аудита завершения операции. Вариант с безусловным вызовом прост, но при отключённом аудите приводит к панике. Вариант с проверкой nil безопасен и явно отражает необязательность callback, однако добавляет условие в каждое место вызова.
Заглушка без побочных эффектов устраняет повторяющиеся проверки, но способна замаскировать ошибку, если аудит на самом деле обязателен. Поэтому для необязательного аудита выбирают проверку nil, а для обязательного — валидацию зависимости при создании сервиса и отказ с ошибкой.
Так контракт становится явным: отсутствие необязательного поведения не вызывает сбой, а ошибочная обязательная зависимость обнаруживается как можно раньше.
Функции нельзя сравнивать друг с другом оператором == или !=. Разрешено только сравнение функционального значения с nil, поэтому проверить, указывают ли две функции на одну реализацию, напрямую нельзя.
Нет. Интерфейс хранит динамический тип и динамическое значение. Если в него поместить переменную функционального типа со значением nil, интерфейс будет ненулевым, поскольку динамический тип уже присутствует; вызов извлечённой функции всё равно может привести к панике.
В defer значение функции и аргументы вычисляются в момент выполнения самого оператора defer, а вызов откладывается до выхода из функции. Поэтому последующее присваивание другой функции не изменит уже отложенный вызов; если в момент defer было сохранено nil, паника возникнет при выполнении отложенного вызова.