Для чего нужен метод Close в Go?
Что проверяют
Понимаете ли вы, что сборщик мусора в Go управляет памятью, но не файлами и соединениями. Следом обычно спрашивают, что делать с ошибкой Close и почему defer в цикле опасен.
Как отвечать
Close() error это метод интерфейса io.Closer, его реализуют *os.File, net.Conn, *sql.Rows, http.Response.Body и многие обёртки. Он освобождает то, чего сборщик мусора не видит:
- файловые дескрипторы: у процесса их лимит, и без закрытия сервис получает
too many open files. У*os.Fileесть страховка через финализатор, но срабатывает она когда-нибудь при сборке мусора, рассчитывать на неё нельзя; - соединения из пула: незакрытый
resp.Bodyне возвращает соединение в пулhttp.Transport, копятся сокеты и горутины. Чтобы соединение переиспользовалось, тело стоит дочитать, напримерio.Copy(io.Discard, resp.Body). Незакрытые*sql.Rowsдержат соединение из пулаdatabase/sql; - сброс данных: у пишущих обёрток
Closeдописывает хвост,gzip.Writerбез него оставит архив без завершающего блока.
Стандартный приём: defer f.Close() сразу после проверки ошибки открытия. При чтении ошибку Close обычно игнорируют, при записи нельзя: о неудачной записи файловая система может сообщить только на закрытии. Поэтому при записи закрывают явно или в defer через именованный результат и errors.Join.
Контракт io.Closer не определяет поведение повторного вызова, это решает конкретный тип. *os.File на втором вызове вернёт ошибку.
Где спотыкаются
Пишут defer resp.Body.Close() до проверки err. При ошибке resp равен nil, и программа падает с nil pointer dereference прямо на строке с defer.
Ставят defer в цикле по файлам. Отложенные вызовы выполняются при выходе из функции, а не из итерации, и на тысяче файлов кончаются дескрипторы. Тело цикла выносят в отдельную функцию.
Путают метод Close со встроенной close(ch). Она закрывает канал и сигналит получателям, к освобождению ресурсов отношения не имеет.