Встречается на 25% собеседований по Go

Зачем нужна конструкция defer

Конструкция defer в Go откладывает выполнение функции до момента выхода из текущей функции, гарантируя, что код выполнится независимо от того, как завершится функция — нормально, через ошибку или панику. Это ключевой механизм для управления ресурсами и очистки, который делает код более надёжным и читаемым.

Как это работает

Когда в функции встречается defer, аргументы функции вычисляются немедленно, но сама функция не вызывается. Вместо этого она помещается в стек отложенных вызовов. При выходе из функции (включая случаи ошибок и паники) отложенные вызовы выполняются в порядке LIFO (last-in, first-out): последний задекларированный defer выполняется первым. Это позволяет корректно закрывать ресурсы в обратном порядке их открытия.

Примеры использования

Освобождение ресурсов

Самый частый сценарий — закрытие файлов, сетевых соединений, мьютексов и других ресурсов. Без defer пришлось бы дублировать код очистки в каждой точке выхода, что увеличивает риск ошибок.

go
func readFile(filename string) (string, error) {
    f, err := os.Open(filename)
    if err != nil {
        return "", err
    }
    defer f.Close() // файл закроется при любом выходе из функции
text
data, err := ioutil.ReadAll(f)
if err != nil {
    return "", err
}
return string(data), nil

}

text

### Разблокировка мьютекса

`defer` гарантирует, что мьютекс будет разблокирован, даже если в критической секции произойдёт паника или ошибка.

```go
var mu sync.Mutex

func process() {
    mu.Lock()
    defer mu.Unlock() // мьютекс разблокируется при выходе из функции

    // операции, которые могут вызвать панику или ошибку
}

Паттерн «открой-закрой»

defer идеально подходит для парных операций: открыть/закрыть, подключить/отключить, начать/завершить. Это упрощает чтение кода, так как логика очистки находится рядом с логикой инициализации.

Подводные камни

  • Аргументы вычисляются сразу. Если в defer передаётся выражение, оно вычисляется в момент объявления, а не в момент выполнения. Например, defer fmt.Println(time.Now()) зафиксирует время вызова defer, а не время выхода из функции.
  • Порядок LIFO. Если несколько defer зависят друг от друга, важно помнить, что они выполняются в обратном порядке. Обычно это желаемое поведение, но его нужно учитывать.
  • Производительность. В старых версиях Go defer имел небольшие накладные расходы, но с Go 1.14 они практически устранены. В большинстве случаев оптимизация не требуется.
  • Не используйте defer в циклах без необходимости. Если defer находится внутри цикла, отложенные вызовы накопятся и выполнятся только при выходе из функции, что может привести к неожиданному поведению или утечкам. Лучше выносить тело цикла в отдельную функцию.

Когда использовать

defer следует использовать всегда, когда нужно гарантировать выполнение очистки или завершающих операций: закрытие файлов, соединений, разблокировка мьютексов, освобождение пулов, снятие блокировок. Это идиоматичный подход в Go, который делает код безопаснее и проще для понимания.

Коротко

  • defer откладывает выполнение функции до выхода из текущей функции, гарантируя выполнение при любом исходе.
  • Отложенные вызовы выполняются в порядке LIFO — последний объявленный выполняется первым.
  • Основное применение — освобождение ресурсов (файлы, мьютексы, соединения) и реализация паттерна «открой-закрой».
  • Аргументы defer вычисляются сразу, а не в момент выполнения — это важно учитывать при работе с изменяемыми значениями.
как отвечать на вопрос
пример собеседования
фреймворки на собеседовании
типичные вопросы junior
интервью вопросы и ответы