Go: Функции
Теория: defer
В реальном коде мы постоянно берём внешние ресурсы: открываем соединения с базой данных, делаем HTTP-запросы, открываем файлы. Каждый такой ресурс нужно обязательно освобождать. Если закрытие забыть, начинается утечка: соединение с базой висит открытым, пул соединений постепенно заполняется, новые запросы ждут, приложение подвисает, а база сыплет ошибками «too many connections». То же самое с HTTP и файлами: дескрипторы остаются занятыми, и рано или поздно система перестаёт нормально работать.
Пример работы с базой данных
defer решает эту боль очень просто. Сразу рядом с «взятием» ресурса можно объявить, что он должен быть закрыт в конце функции. Дальше можем реализовывать логику как нужно, выходишь по ошибкам или по успеху, — уборка всё равно произойдёт автоматически. Это значит, что не нужно держать в голове десятки мест, где поставить Close(), и не нужно бояться, что при рефакторинге забудешь что-то убрать или добавить.
То есть: взял ресурс → тут же рядом поставил deferred-уборку → и забыл.
При любом выходе — успешном, через return, при ошибке, даже при панике — deferred-вызовы выполняются.
Пример без defer
Такой код формально работает, но поддерживать его тяжело. В каждом пути выхода нужно помнить о db.Close(), и одна забытая строчка обернётся проблемой на продакшене.
Пример с defer
Здесь принцип простой: взял ресурс → сразу же повесил его освобождение через defer → дальше пишешь логику и не думаешь о Close().
Та же история с HTTP
Если забыть Close(), то соединение останется занято, и пул начнёт переполняться. С defer всё решается автоматически.
Где ставить defer
defer используют сразу после того, как ты получил ресурс, который нужно освободить.
То есть правило простое:
- Открыл файл → сразу же
defer file.Close(). - Сделал HTTP-запрос → сразу же
defer resp.Body.Close(). - Подключился к базе → сразу же
defer db.Close(). - Залочил мьютекс → сразу же
defer mu.Unlock().
Почему сразу? Потому что в момент получения ресурса мы точно знаем, что он был взят. Дальше в функции начинаются проверки, условия и возвраты, и вероятность забыть об освобождении резко возрастает. Если же defer ставится сразу, уборка гарантируется автоматически, и об этом больше не приходится заботиться.
Как ставить defer
Синтаксис очень простой: пишешь defer перед вызовом функции.
Где someFunc — это та функция, которая освобождает ресурс.
Например:
Важный момент
defer всегда сработает в конце текущей функции, даже если выйдешь через return или произойдёт ошибка. Поэтому его ставят в том месте, где ресурс берётся, а не где-то в конце.
Два правила defer
Аргументы фиксируются сразу. Если в defer передать переменную, её значение берётся в тот же момент. Все изменения дальше уже не повлияют.
Если нужно вывести последнее значение, используют функцию-замыкание:
Выполняются в обратном порядке. Если в функции несколько defer, они будут вызваны в порядке «последний объявлен — первый выполнен».
Результат:
Это удобно и логично: если открыть ресурс А, потом ресурс B, то закрываться они должны в обратном порядке — сначала B, потом А.
defer — это страховка от человеческой невнимательности. Он гарантирует, что все открытые ресурсы будут закрыты в нужный момент: база — освободит соединение, HTTP — отдаст сокет обратно, файл — закроет дескриптор. Это работает одинаково для любых подобных задач и избавляет от классовых багов, связанных с забытым Close().


