Common Lisp предоставляет функциональность для выхода из сложного процесса в нелокальном, динамических ограниченном стиле. Для этих целей есть два вида операторов, называемых catch и throw. Форма catch выполняет некоторые подформы так, что если форма throw выполняется в ходе этих вычислений, в данной точке вычисления прерываются и форма catch немедленно возвращает значение указанное в throw. В отличие от block и return (раздел 7.7), которые позволяют выйти из тела block из любой точки лексически, находящейся внутри тела, catch/throw механизм работает, даже если форма throw по тексту не находится внутри тела формы catch. Throw может использовать только в течение (продолжительности времени) выполнения тела catch. Можно провести аналогию между ними, как между динамически связываемыми переменными и лексически связываемыми.
Оператор catch служит мишенью для передачи управления с помощью throw. Первой выполняется форма tag для создания объекта для имени catch. Он может быть любым Lisp’овым объектом. Затем устанавливается ловушка с тегом, в качестве которого используется этот объект. Формы form выполняются как неявный progn, и возвращается результат последней формы. Однако если во время вычислений будет выполнена форма throw с тегом, который равен eq тегу catch, то управление немедленно прерывается и возвращается результат указанный в форме throw. Ловушка, устанавливаемая с помощью catch, упраздняется перед тем, как будет возвращён результат.
Тег используется для соотнесения throws и catches. (catch ’foo form) будет перехватывать (throw ’foo form), но не будет (throw ’bar form). Если throw выполнен без соответствующего catch, готового его обработать, то это является ошибкой.
Теги catch сравниваются с использованием eq, а не eql. Таким образом числа и буквы не могут использоваться в качестве тегов.
Иногда необходимо выполнить форму и быть уверенным, что некоторые побочные эффекты выполняются после её завершения. Например:
Функциональность нелокальных выходов в Common Lisp создаёт ситуацию, в которой однако данный код может не сработать правильно. Если drill-hole может бросить исключение в ловушку, находящуюся выше данного progn, то (stop-motor) никогда не выполниться. Более удобный пример с открытием/закрытием файлов:
где закрытие файла может не произойти, по причине ошибки в функции process-file.
Для того, чтобы вышеприведённый код работал корректно, можно переписать его с использованием unwind-protect:
Если drill-hole бросит исключение, которое попытается выйти из блока unwind-protect, то (stop-motor) будет обязательно выполнена.
Этот пример допускает то, что вызов stop-motor корректен, даже если мотор ещё не был запущен. Помните, что ошибка или прерывание может осуществить выход перед тем, как будет проведена инициализация. Любой код по восстановлению состояния должен правильно работать вне зависимости от того, где произошла ошибка. Например, следующий код неправильный:
Если выход случиться перед тем, как выполниться операция incf, то выполнение decf приведён к некорректному значению в *access-count*. Правильно будет записать этот код так:
Как правило, unwind-protect гарантирует выполнение форм cleanup-form перед выходом, как в случае нормального выхода, так и в случае генерации исключения. (Если, однако, выход случился в ходе выполнения форм cleanup-form, никакого специального действия не предпринимается. Формы cleanup-form не защищаются. Для этого необходимо использовать дополнительные конструкции unwind-protect.) unwind-protect возвращает результат выполнения защищённой формы protected-form и игнорирует все результаты выполнения форм чистки cleanup-form.
Следует подчеркнуть, что unwind-protect защищает против всех попыток выйти из защищённой формы, включая не только «динамический выход» с помощью throw, но и также «лексический выход» с помощью go и return-from. Рассмотрим следующую ситуацию:
Когда выполнится go, то сначала выполнится print, а затем перенос управления на тег out будет завершён.
Оператор throw переносит управление к соответствующей конструкции catch. Сначала выполняется tag для вычисления объекта, называемого тег throw. Затем вычисляется форма результата result, и этот результат сохраняется (если форма result возвращает несколько значений, то все значения сохраняются). Управление выходит и самого последнего установленного catch, тег которого совпадает с тегом throw. Сохранённые результаты возвращаются, как значения конструкции catch. Теги catch и throw совпадают, только если они равны eq.
В процессе, связывания динамических переменных упраздняются до точки catch, и выполнятся все формы очистки в конструкциях unwind-protect. Форма result вычисляется перед началом процесса раскручивания, и её значение возвращается из блока catch.
Если внешний блок catch с совпадающим тегом не найден, раскрутка стека не происходит и сигнализируется ошибка. Когда ошибка сигнализируется, ловушки и динамические связывания переменных являются теми, которые были в точке throw.