golang defer

Пользователь

от anastacio.hane , в категории: Общие вопросы , 4 года назад

Хочу узнать, как в Golang’е сделать отложенный вызов функции для запуска непосредственно перед выполнением самой функции, подскажите, пожалуйста.


Facebook Vk Ok Twitter LinkedIn Telegram Whatsapp Pocket

6 ответов

Пользователь

от tessie_jacobs , 4 года назад

Оператор 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 
}


Пользователь

от tessie_jacobs , 4 года назад

Отсрочка вызова функции, такой как Close, имеет два преимущества.

Во-первых, это гарантирует, что вы никогда не забудете закрыть файл, ошибка, которую легко допустить, если вы позже отредактируете функцию, чтобы добавить новый путь возврата.

Во-вторых, это означает, что закрытие находится рядом с открытием, что гораздо понятнее, чем помещение его в конец функции.

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

1
2
3
for i := 0; i < 5; i++ {
   defer fmt.Printf("%d ", i)
}


Пользователь

от tessie_jacobs , 4 года назад

Отложенные функции выполняются в порядке 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")
   // делаем что-нибудь....
}


Пользователь

от tessie_jacobs , 4 года назад

Мы можем добиться большего успеха, используя тот факт, что аргументы отложенных функций оцениваются при выполнении 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 может показаться странным, но его интересные и мощные приложения исходят именно из того, что он не на основе блоков, а на основе функций.


Пользователь

от newell , 4 года назад

Можешь зайти на хабр, там ещё в две тысячи одиннадцатом году была выложена статья - Обработка ошибок в Go: Defer, Panic и Recover. Она будет очень полезно для тебя.


Пользователь

от anastacio.hane , 4 года назад

Спасибо большое, ребят, очень подробно всё объяснили. Даже такой, как я – практически полный ноль, смог понять. Буду теперь пробовать это на практике.