Материал проходит вычитку — возможны неточности.
Урок 54 из 118

defer в цикле и os.Exit

Аня на ревью указала на цикл импорта: уборка копилась до конца функции, и сервис упирался в лимит дескрипторов. А старая утилита выгрузки завершалась так, что отложенная уборка не выполнялась вовсе. Миша починил оба места.

Главная мысль

defer живёт до выхода из функции, поэтому в цикле он копит отложенные вызовы, а os.Exit не выполняет их вообще.

Каждая итерация цикла добавляет новый отложенный вызов, и все они ждут выхода из функции, а не конца итерации.

Defer в цикле копит ресурсы

for _, path := range paths {
    f, err := os.Open(path)
    if err != nil {
        return err
    }
    defer f.Close() // файлы копятся до выхода из функции
}

Тысяча путей — тысяча открытых файлов до самого конца функции: рано или поздно упрёшься в лимит дескрипторов. На горячем пути импорта так и случилось.

Вынести тело итерации в функцию

Правильное решение — граница функции. Именно она является границей жизни defer:

for _, path := range paths {
    if err := process(path); err != nil {
        return err
    }
}

func process(path string) error {
    f, err := os.Open(path)
    if err != nil {
        return err
    }
    defer f.Close()
    // работа с файлом
    return nil
}

Не хочется заводить отдельную функцию — оберни тело цикла в анонимную с немедленным вызовом, эффект тот же.

Os.Exit не выполняет defer

Один способ выйти из программы defer не переживает:

defer fmt.Println("не напечатается")
os.Exit(1)

os.Exit завершает процесс немедленно, не выполняя отложенные вызовы. Это не возврат из функции, а выход из программы. os.Exit — инструмент для фатальных ситуаций в main или при старте, а не способ завершить работу «аккуратно». Если нужна аккуратная остановка — возвращай ошибку наверх и дай defer сделать своё дело.

В следующем уроке

Вы узнаете:

  • почему тысяча открытых файлов упирается в лимит ОС
  • как анонимная функция с немедленным вызовом заменяет именованную
Задание · вопросы

Проверьте себя

Вопрос 1 из 3

defer fmt.Println("не напечатается"); os.Exit(1). Что напечатается?

Вопрос 2 из 3

defer f.Close() внутри цикла for — что произойдёт?

Вопрос 3 из 3

Как исправить defer в цикле?

Итоги

defer привязан к функции, а не к блоку или итерации. В цикле — выноси тело в функцию. os.Exit — не возврат: отложенные вызовы он пропускает.

Наш сайт собирает куки. Чтобы тебе было удобнее им пользоваться. Нажимая на кнопку, ты даёшь разрешение собирать куки