defer и panic/recover
defer — гарантированный cleanup
defer откладывает выполнение функции до момента возврата из текущей функции. Выполняется всегда — при нормальном return и при panic.
func readFile(path string) error {
f, err := os.Open(path)
if err != nil {
return err
}
defer f.Close() // закроется в любом случае
// работаем с файлом...
return nil
}Без defer пришлось бы писать f.Close() перед каждым return.
LIFO-порядок
Несколько defer выполняются в обратном порядке (последний объявленный — первым):
func example() {
defer fmt.Println("третий")
defer fmt.Println("второй")
defer fmt.Println("первый")
}
// Вывод:
// первый
// второй
// третийЭто удобно для вложенных ресурсов: открыл A, открыл B → при закрытии сначала B, потом A.
Аргументы вычисляются сразу
Аргументы функции в defer вычисляются в момент объявления, не выполнения:
x := 1
defer fmt.Println(x) // захватывает x = 1
x = 2
// При return напечатает: 1Чтобы захватить значение на момент выполнения — используй замыкание:
x := 1
defer func() { fmt.Println(x) }() // захватит x при выполнении
x = 2
// При return напечатает: 2defer и именованные возвращаемые значения
defer может изменить возвращаемое значение через именованные переменные:
func double(n int) (result int) {
defer func() {
result *= 2 // модифицирует result перед возвратом
}()
result = n
return
}
fmt.Println(double(5)) // 10Это используется для оборачивания ошибок и добавления контекста.
panic — критическая ошибка
panic останавливает нормальное выполнение и начинает «раскручивать стек» — выполняет все defer по пути вверх:
func mustPositive(n int) int {
if n <= 0 {
panic(fmt.Sprintf("ожидалось положительное число, получено %d", n))
}
return n
}panic используй только для действительно невосстановимых ситуаций: нарушение инварианта, программная ошибка. Для обычных ошибок возвращай error.
recover — перехват паники
recover() останавливает раскрутку стека и возвращает значение, переданное в panic. Работает только внутри defer:
func safeDiv(a, b int) (result int, err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("перехвачена паника: %v", r)
}
}()
return a / b, nil // паникует при b = 0
}
result, err := safeDiv(10, 0)
fmt.Println(result, err) // 0 перехвачена паника: runtime error: ...Типичные паттерны с defer
Мьютекс:
mu.Lock()
defer mu.Unlock()Транзакция:
tx, _ := db.Begin()
defer func() {
if err != nil {
tx.Rollback()
} else {
tx.Commit()
}
}()Логирование времени выполнения:
func timed(name string) func() {
start := time.Now()
return func() {
fmt.Printf("%s took %v\n", name, time.Since(start))
}
}
defer timed("loadUsers")()Обрати внимание на двойные скобки ()() — первые вызывают timed (захватывая start), вторые — регистрируют возвращённую функцию как defer.