defer: выполнить при выходе
Импорт каталога потихоньку съедал дескрипторы: файлы открывались, а закрытие терялось где-то в ветках. На больших пачках сервис упирался в лимит операционной системы. Миша привязал закрытие к самому открытию.
defer — «выполни этот вызов, когда функция будет выходить», на любом пути: обычный return, return из середины или паника.
Классический пример — закрыть файл сразу после открытия, чтобы не таскать Close по всем веткам кода.
Закрытие файла
func readConfig(path string) ([]byte, error) {
f, err := os.Open(path)
if err != nil {
return nil, err
}
defer f.Close()
return io.ReadAll(f)
}
Без defer пришлось бы писать f.Close перед return io.ReadAll(f). Пока веток одна, разницы нет, но стоит добавить проверку ошибки посередине — и Close надо не забыть вставить в каждую новую точку выхода. defer ставит очистку рядом с инициализацией и гарантирует, что она выполнится, откуда бы функция ни вышла. Именно так Миша заткнул течь дескрипторов в импорте.
Порядок — как стек
Отложенные вызовы выполняются в обратном порядке объявления, последний объявленный — первым.
Что здесь показано
- Стек — локальные переменные функции.
i = 3— значение;p = ptr → 10— указатель на значение;b = "hello"— срез (заголовок: указатель + длина + ёмкость). - Куча — данные, на которые ссылаются переменные:
rodata— байты строки-литерала (в бинарнике),данные N Б— бэкинг-массив слайса,строка— динамическая строка. - Горутины — лёгкие потоки Go: статус (running/runnable/waiting…), функция, в которой каждая сейчас находится, размер стека (растёт по мере работы) и пометка «из main» — горутина запущена из твоего кода.
- GC — сборщик мусора: фаза (off/mark) и число завершённых циклов.
Для вложенных ресурсов это удобно: открыл файл, потом взял блокировку — при выходе сначала снимется блокировка, потом закроется файл, в правильном порядке.
Вы узнаете:
- как с Go 1.14 компилятор оптимизирует простые defer
- почему очистка с defer срабатывает даже при панике
Проверьте себя
defer fmt.Println("первый"); defer fmt.Println("второй"); defer fmt.Println("третий"). Какой порядок вывода?
Зачем нужен defer?
Где идиоматично ставить defer?
правильных ответов
defer — это про гарантию очистки. Увидел пару «взял ресурс — надо вернуть» — ставь defer: mu.Lock() рядом с defer mu.Unlock(), os.Open рядом с defer f.Close(). Это идиома, по которой Go-код узнаётся с одного взгляда, и она убирает целый класс багов с утечками и забытыми ветками.
10 000 ₽ · 60 минут · онлайн