Открывая доступ к ресурсу (файлу, сокету, сетевому соединению), программист думает, как бы потом не забыть его закрыть. И забывает.
Под ресурсом понимаем любой объект, который может быть открыт (получаем доступ) и закрыт (отдаём доступ). Если наш код возьмёт доступ и не отдаст — будет не очень.
Note
Обычно в языках ООП такое решается с помощью контекстных менеджеров (Python) или деструкторов (C++, Java). Однако в Gо это происходит по-другому — через механизм отложенного вызова.
В Go есть оператор, который позволяет запланировать отложенный вызов, — это инструкция defer
. Мы рассматривали его кратко в уроке «Особенности языка Go», а сейчас разберём в подробностях.
Оператор defer
часто применяется на практике, вот только для начинающих не всегда очевидно, как он работает и какие есть подводные камни.
Оператор defer
используют внутри функции, а его операндом служит выражение вызова функции. Будем называть эти функции «отложившая» и «отложенная», чтобы избежать путаницы.
Инструкция defer
вычисляет аргументы для вызова, но вызов не делает. Вызов выполняется непосредственно перед тем, как отложившая его функция вернёт управление.
Выведет:
evaluated
deferred
Работу оператора можно описать следующим образом.
- Идёт обычное выполнение программы.
- Наступает очередь выполнения оператора
defer
. - Вычисляются операнды отложенной функции, если такие есть.
- Вызов функции вместе со значениями откладывается в специальный стек.
- Выполнение функции продолжается. Если встречается оператор
defer
, то повторяем пункты 3 и 4. - Если встречается оператор
return
, то функция вычисляет его операнды и сохраняет значение в буфер. - Если стек отложенных вызовов не пустой, то извлекаем из него вызов функции и выполняем его.
- Повторяем пункт 7, пока стек не опустеет.
- Выходим из функции, возвращая значение из буфера.
Важно понимать, что результат функции вычисляется до выполнения отложенных вызовов.
Отложенных вызовов может быть несколько. Тогда они выполняются в обратном порядке, то есть начиная с того, который был отложен последним, так как вызовы складывались в стек.
Отложенная функция может возвращать значения, но они будут проигнорированы и не могут быть использованы.
Также отложенная функция может быть анонимной и заданной литерально. Напомним, что анонимной называется функция, задаваемая литералом по месту использования. Анонимная функция в таком случае задаётся сразу вместе с вызовом.
В таком случае ей могут быть доступны переменные отложившей функции. Произойдёт замыкание (closure). Например, если у отложившей функции есть именованное возвращаемое значение, отложенная функция может его изменить.
Обратите внимание, это работает только с именованными возвращаемыми значениями. Следующий код выведет "Казалось бы"
:
В чём разница? В первом случае функция возвращает переменную value
. При вычислении операнда return
ей действительно присваивается значение "Казалось бы"
, но эта переменная захвачена замыканием и изменяется в нём. После чего она и возвращается из функции.
Во втором случае у нас есть некоторая скрытая переменная ret1
, в которую при вызове оператора return
копируется значение её операнда. После любые действия с value
уже не будут важны.
Также распространённой ошибкой является предположение, что операнды отложенной функции будут вычислены во время её выполнения. Это не так, они вычисляются при выполнении оператора defer
:
Программа напечатает "some text"
.
Оператор defer
чаще всего можно увидеть с парными функциями Open()
/Close()
, Lock()
/Unlock()
. Его ставят сразу после захвата ресурса, чтобы точно не забыть.
Вот классический пример:
Пример применения
Реализуем на основе функции defer
замер времени выполнения функции.
Для начала создадим функцию, которая будет измерять время выполнения и выводить его на экран.
Теперь применим её внутри какой-нибудь функции.
defer и panic
Во время выполнения программы могут возникнуть различные непредвиденные обстоятельства, из-за которых дальнейшая работа функции будет невозможна. В таком случае выполнение функции немедленно прекращается, паника передаётся вызывающей функции и затем вверх по стеку, пока выполнение программы не завершится.
Однако этот процесс меняется, если паникующая функция имеет отложенные вызовы. Они будут исполнены после выхода из функции и могут понять, что произошла паника.
Это похоже на механизм исключений в С++ и Python, однако крайне не рекомендуется использовать panic
для обычной работы. Вызывать панику следует лишь тогда, когда выполнение программы действительно не может продолжаться и она должна быть завершена.
Без применения оператора defer
остановить панику было бы невозможно. Он позволяет вклиниться в стек вызовов функций и остановить её. Заметьте, что не всякую панику можно восстановить. Иногда возникают особые ситуации, когда recover
не срабатывает.
📂 Go | Последнее изменение: 22.08.2024 13:38