go: почему defer внутри цикла может стать проблемой 🔌

В Go есть удобная конструкция defer. Она откладывает выполнение вызова до выхода из текущей функции.

Но есть нюанс: defer внутри цикла срабатывает не в конце итерации, а при завершении всей функции.

Например:

func fetchAll(urls []string) error { for _, url := range urls { resp, err := http.Get(url) if err != nil { return err } defer resp.Body.Close() // Обрабатываем ответ } return nil }

Если нужно обработать тысячу URL, вызовы Close() будут отложены до выхода из fetchAll. В результате тела ответов могут оставаться открытыми, хотя обработка каждого из них уже закончилась.

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

Вынести обработку одного ответа в отдельную функцию:

func fetchOne( client *http.Client, url string, ) error { resp, err := client.Get(url) if err != nil { return err } defer resp.Body.Close() _, err = io.Copy(io.Discard, resp.Body) return err }

Теперь defer сработает при выходе из fetchOne, то есть после обработки каждого ответа.

А цикл останется простым:

for _, url := range urls { if err := fetchOne(http.DefaultClient, url); err != nil { return err } }

Здесь есть ещё один нюанс: для повторного использования HTTP/1.x-соединений обычно нужно дочитать тело ответа до конца и закрыть его. Одного вызова Close() не всегда достаточно для повторного использования соединения.

Вывод: defer сам по себе не проблема.

Важно понимать, к какой функции он привязан и когда именно выполнится.

А вы встречали defer внутри больших циклов в рабочем коде?