Оператор defer
в Go планирует вызов функции (отложенной функции) для запуска непосредственно перед выполнением функцией defer
возврата (return). Это необычный, но эффективный способ справиться с такими ситуациями, как ресурсы, которые должны быть освобождены независимо от того, какой путь функция принимает для возврата. Каноническими примерами являются разблокировка мьютекса или закрытие файла.
// Contents возвращает содержимое файла как строку.
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). Это помогает избежать излишних забот о переменных, меняющих значения при выполнении функции, это также означает что одно место отложенного вызова может отложить исполнение несколько функций. Вот простой пример.
for i := 0; i < 5; i++ {
defer fmt.Printf("%d ", i)
}
Отложенные функции выполняются в порядке LIFO (last-in-first-out - последним зашел, первым вышел), поэтому этот код вызовет 4 3 2 1 0
для печати после возврата из функции. Более правдоподобный пример - простой способ отследить выполнение функции через программу. Мы могли бы написать пару простых трассировок рутины как следующие:
func trace(s string) { fmt.Println("entering:", s) }
func untrace(s string) { fmt.Println("leaving:", s) }
// Используем их:
func a() {
trace("a")
defer untrace("a")
// делаем что-нибудь....
}
Мы можем добиться большего успеха, используя тот факт, что аргументы отложенных функций оцениваются при выполнении defer
. Процедура трассировки может установить аргумент для процедуры отслеживания. Этот пример:
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
- Эффективный Go: повторная декларация и переназначение
- Эффективный Go: цикл for
Комментариев нет:
Отправить комментарий