Оператор defer в Go планирует вызов функции (отложенной функции) для запуска непосредственно перед выполнением функцией defer возврата (return). Это необычный, но эффективный способ справиться с такими ситуациями, как ресурсы, которые должны быть освобождены независимо от того, какой путь функция принимает для возврата. Каноническими примерами являются разблокировка мьютекса или закрытие файла.
// Contents возвращает содержимое файла как строку.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
func Contents(filename string) (string, error) { f, err := os.Open(filename) if err != nil { return "", err } // f.Close будет выполнена когда мы закончим. defer f.Close() var result []byte buf := make([]byte, 100) for { n, err := f.Read(buf[0:]) result = append(result, buf[0:n]...) if err != nil { if err == io.EOF { break } // f будет закрыта если мы делаем возврат здесь return "", err } } // f будет закрыта если мы делаем возврат здесь return string(result), nil } |
Отсрочка вызова функции, такой как Close, имеет два преимущества.
Во-первых, это гарантирует, что вы никогда не забудете закрыть файл, ошибка, которую легко допустить, если вы позже отредактируете функцию, чтобы добавить новый путь возврата.
Во-вторых, это означает, что закрытие находится рядом с открытием, что гораздо понятнее, чем помещение его в конец функции.
Аргументы для отложенной функции (которые включают получателя, если функция является методом) оцениваются, когда defer выполняется, а не тогда, когда выполняется вызов (call). Это помогает избежать излишних забот о переменных, меняющих значения при выполнении функции, это также означает что одно место отложенного вызова может отложить исполнение несколько функций. Вот простой пример.
1 2 3 |
for i := 0; i < 5; i++ { defer fmt.Printf("%d ", i) } |
Отложенные функции выполняются в порядке LIFO (last-in-first-out - последним зашел, первым вышел), поэтому этот код вызовет 4 3 2 1 0 для печати после возврата из функции. Более правдоподобный пример - простой способ отследить выполнение функции через программу. Мы могли бы написать пару простых трассировок рутины как следующие:
1 2 3 4 5 6 7 8 9 |
func trace(s string) { fmt.Println("entering:", s) } func untrace(s string) { fmt.Println("leaving:", s) } // Используем их: func a() { trace("a") defer untrace("a") // делаем что-нибудь.... } |
Мы можем добиться большего успеха, используя тот факт, что аргументы отложенных функций оцениваются при выполнении defer. Процедура трассировки может установить аргумент для процедуры отслеживания. Этот пример:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
func trace(s string) string { fmt.Println("entering:", s) return s } func un(s string) { fmt.Println("leaving:", s) } func a() { defer un(trace("a")) fmt.Println("in a") } func b() { defer un(trace("b")) fmt.Println("in b") a() } func main() { b() } |
печатает
entering: b
in b
entering: a
in a
leaving: a
leaving: b
Для программистов, привыкших к управлению ресурсами на уровне блоков на других языках, defer может показаться странным, но его интересные и мощные приложения исходят именно из того, что он не на основе блоков, а на основе функций.
Можешь зайти на хабр, там ещё в две тысячи одиннадцатом году была выложена статья - Обработка ошибок в Go: Defer, Panic и Recover. Она будет очень полезно для тебя.
Спасибо большое, ребят, очень подробно всё объяснили. Даже такой, как я – практически полный ноль, смог понять. Буду теперь пробовать это на практике.