Перейти к основному содержимому
Модуль 3 · Урок 5 из 515/50 в курсе~12 min
Уроки модуля (5/5)

`defer`: гарантированная очистка

defer откладывает выполнение вызова до тех пор, пока не будет включена включающая функция. возвращается. Он запускается, даже если функция завершается из-за паники. Несколько defer в той же функции выполняются в порядке LIFO (последний вошел, первый вышел).

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

Синтаксис и канонический вариант использования

Go
f, err := os.Open(path)
if err != nil {
    return err
}
defer f.Close()   // chiamata SEMPRE all'uscita della funzione

// ... usa f liberamente ...
return nil

Типично: f.Close(), mu.Unlock(), db.Close(), tx.Rollback(), recover().

Аргументы оцениваются немедленно, выполнение откладывается.

Когда вы пишете defer foo(x), x оценивается во время defer, не во время выполнения:

Go
x := 10
defer fmt.Println(x)   // cattura 10
x = 99
// quando la funzione ritorna stampa "10", non "99"

заказ ЛИФО

Откладывает стек и выполняет, начиная с самого последнего:

Go
defer fmt.Println(1)
defer fmt.Println(2)
defer fmt.Println(3)
// output: 3, 2, 1

Это согласуется с семантикой ресурса: последнее открытое является первым. быть закрытым.

defer + recover: обработка паники

defer — единственный способ перехватить panic:

Go
func safe() {
    defer func() {
        if r := recover(); r != nil {
            fmt.Println("recuperato:", r)
        }
    }()
    panic("boom")
}

Ошибка: defer в цикле

Помещение defer f.Close() в цикл накапливает отсрочки ** до конца функция**, а не каждой итерации. В длинном цикле вы открываете много файлов и закрывать их только при выходе из функции: риск утечки файлового дескриптора.

Go
// MALE: defer accumulati
for _, path := range paths {
    f, _ := os.Open(path)
    defer f.Close()   // chiude TUTTI alla fine della funzione esterna
    // ...
}

// MEGLIO: estrai in una funzione
for _, path := range paths {
    elabora(path)   // ogni chiamata ha il proprio scope di defer
}

Попробуйте это

Упражнение#go.m3.l5.e1
Попыток: 0Загрузка…

Выведите «inizio», затем отложите печать «fine» с помощью defer, затем напечатайте «mezzo». Результат должен быть: inizio/mezzo/fine.

Загрузка редактора…
Показать подсказку

Отсрочка запускается, когда функция main() собирается вернуться.

Решение доступно после 3 попыток

Упражнение#go.m3.l5.e2
Попыток: 0Загрузка…

Напишите три последовательных отсрочки, которые печатают 1, 2 и 3 в указанном порядке в коде. Выход будет 3/2/1 (ЛИФО).

Загрузка редактора…
Показать подсказку

Три отсрочки подряд; помните, что они выполняются в обратном порядке.

Решение доступно после 3 попыток

Quiz#go.m3.l5.e3
Готово

Что это печатает? (примечание: аргументы отсрочки оцениваются немедленно)

Go
x := 10
defer fmt.Println(x)
x = 99
Варианты ответа

Резюме

  • defer call() откладывает выполнение до тех пор, пока функция не вернется.
  • Множественные отсрочки = заказ ЛИФО.
  • Аргументы оцениваются немедленно, выполнение отложено.
  • Используется для детерминированной очистки: файлов, мьютексов, соединений.
  • defer + recover() перехватывает panic (редко, на границах).
  • Никогда не используйте defer внутри длинного цикла: он накапливается до тех пор, пока внешняя функция не вернет результат.