Defer, Panic и Recover в Go
Это перевод статьи Defer, Panic, and Recover из официального блога The Go Blog.
Go имеет обычные операторы для управления ходом программы: if, for, switch, goto. Также существует оператор go для запуска кода в отдельной горутине (goroutine). Я бы хотел обсудить некоторые из менее обычных: defer, panic и recover.
Defer
Оператор defer
кладёт функцию в специальный список — список сохранённых вызовов, который извлекается после того, как внешняя функция вернула результат. Defer
обычно используется для упрощения работы функций, выполняющих различные зачистки после своей работы.
Например, давайте посмотрим на функцию, которая открывает два файла и копирует содержимое одного файла в другой:
Это сработает, но есть один баг. Если вызов os.Create
не срабатывает, функция выполняется без закрытия файла. Это легко можно исправить, разместив вызов src.Close
до второго return
. Но если функции были бы сложнее, проблема могла быть не так легко найдена и решена. Путём введения оператора defer
мы можем гарантировать, что файлы будут всегда закрыты.
Оператор defer
позволяет думать о закрытии каждого файла сразу после его открытия, гарантируя, что независимо от количества операторов return
в функции файлы будут закрыты.
Поведение defer
прямолинейно и предсказуемо. Вот три простых правила:
1. Вычисление аргументов отложенной функции происходит, когда происохдит анализ отложенного выражения
В этом примере выражение i
рассчитывается, когда выполнился отложенный вызов Println
. Этот вызов выведет 0 после выполнения функции.
2. Вызов отложенной функции происходит в порядке Last In First Out после того, как выполнилась внешняя функция.
Эта функция выводит “3210”:
3. Отложенные функции могут читать и присваивать значения именованным возвращаемым переменным внешней функции.
В этом примере отложенная функция инкрементирует возвращаемое значение i
после того, как внешняя функция выполнилась. Таким образом, эта функция возвращает 2:
Panic - это встроенная функция, которая останавливает обычный режим работы программы и начинает панику. Когда функция F
вызывает panic
, работа функции прекращается, любые отложенные функции внутри F
выполняются, как обычно, и затем F
возвращает управление к вызвавшему её коду. Для вызывающего кода F
ведёт себя так будто подаёт призыв к панике. Процесс продолжается вверх по стеку, пока все функции текущей горутины не закончат своё выполнение, после чего программа завершает свою работу. Можно инициировать панику, вызвав panic
напрямую. Также паники (panics) могут быть связаны с ошибками времени выполнения, как, например, обращение к элементам массива за его границей.
Recover - это встроенная функция, которая восстанавливает контроль над паникующей горутиной. Recover полезен только внутри отложенных функций. Во время обычного выполнения вызов recover
вернёт nil
и ни на что не повлияет. Если текущая горутина паникует, вызов recover
получит значение, переданное panic
и вернётся к обычной работе.
Вот пример программы, которая демонстрирует, как работает panic
и defer
:
Функция g
принимает int i
и паникует, если i > 3
или если она вызывает сама себя с аргументом i+3
. Функция f
откладывает выполнение функции, которая вызывает recover
и выводит сохранённое значение (если оно отличается от nil
). Попробуйте подумать, каким будет вывод это программы, прежде чем продолжить чтение.
Вывод этой программы будет таким:
Calling g.
Printing in g 0
Printing in g 1
Printing in g 2
Printing in g 3
Panicking!
Defer in g 3
Defer in g 2
Defer in g 1
Defer in g 0
Recovered in f 4
Returned normally from f.
Если мы удалим отложенную функция из f
, то panic
не будет покрыт recovered
и достигнет вершины стека вызовов горутины (goroutine’s call stack), закончив выполнение программы. Обновлённая программа выведет:
Calling g.
Printing in g 0
Printing in g 1
Printing in g 2
Printing in g 3
Panicking!
Defer in g 3
Defer in g 2
Defer in g 1
Defer in g 0
panic: 4
panic PC=0x2a9cd8
[трассировка стека опускается]
Рассмотрим пример использования panic/recover в реальном мире, взглянув на пакет json из стандартной библиотеки Go. Она декодирует JSON-данные во множество рекурсивных функций. Когда встречается неправильный JSON, обработчик вызывает panic для освобождения стека от вызова функции, которая восстанавливается из паники и возвращает соответствующее значение ошибки (см. методы ‘error’ и ‘unmarshal’ типа decodeState в decode.go)
Соглашение о библиотеках Go таково, что даже когда пакет паникует внутри, его внешний API всё равно даёт явные значения возвращаемых ошибок.
Другие случаи использования defer
(помимо примера с file.Close
, данного ранее) включают в себя освобождение mutex
:
вывод футера:
и т. д.
Итак, оператор defer
(с и без panic/recover) предоставляет необычный и мощный механизм управления ходом работы программы. Он может использоваться для проектирования ряда фич, которые в других языках программирования реализованны при помощи специальных структур.