Конструкция unwind-protect необходима всякий раз, когда вы временно приводите структуру данных в несогласованное состояние; она позволяет вам восстановить согласованность данных в случае ошибки или вызова throw. (Другая, более специфическая конструкция очистки, используемая только для изменений содержимого буферов — это атомарная группа изменений; Атомарные группы изменений.)
unwind-protect выполняет body-form с гарантией того, что cleanup-forms будут вычислены, если управление покинет body-form, независимо от того, как это произойдёт. body-form может завершиться нормально, выполнить throw из unwind-protect или вызвать ошибку; во всех случаях cleanup-forms будут вычислены.
Если body-form завершается нормально, unwind-protect возвращает значение body-form после вычисления cleanup-forms. Если body-form не завершается, unwind-protect не возвращает никакого значения в обычном смысле.
Только body-form защищён конструкцией unwind-protect. Если какая-либо из cleanup-forms сама совершит неконтекстный выход (через throw или ошибку), то unwind-protect не гарантирует вычисление остальных. Если сбой одной из cleanup-forms может привести к проблемам, защитите её ещё одним unwind-protect вокруг этой формы.
Например, здесь мы создаём невидимый буфер для временного использования и гарантируем его уничтожение перед завершением:
(let ((buffer (get-buffer-create " *temp*")))
(with-current-buffer buffer
(unwind-protect
body-form
(kill-buffer buffer))))
Можно подумать, что мы могли бы просто написать (kill-buffer (current-buffer)) и обойтись без переменной buffer. Однако показанный выше способ безопаснее, если body-form случайно вызовет ошибку после переключения на другой буфер! (Как вариант, можно обернуть body-form в save-current-buffer, чтобы временный буфер снова стал текущим к моменту его уничтожения.)
Emacs включает стандартный макрос with-temp-buffer, который раскрывается примерно в тот же код, что показан выше (см. Текущий буфер). Некоторые макросы, описанные в этом руководстве, используют unwind-protect аналогичным образом.
Вот реальный пример, взятый из пакета FTP. Он создаёт процесс (см. Процессы) для попытки установить соединение с удалённой машиной. Поскольку функция ftp-login весьма подвержена множеству проблем, которые автор функции не может предусмотреть, она защищена формой, гарантирующей удаление процесса в случае сбоя. В противном случае Emacs может заполниться бесполезными дочерними процессами.
(let ((win nil))
(unwind-protect
(progn
(setq process (ftp-setup-buffer host file))
(if (setq win (ftp-login process host user password))
(message "Выполнен вход")
(error "Ошибка входа по FTP")))
(or win (and process (delete-process process)))))
В этом примере есть небольшая ошибка: если пользователь нажмёт C-g для выхода, и выход произойдёт сразу после возврата из функции ftp-setup-buffer, но до того, как переменная process будет установлена, процесс не будет уничтожен. Исправить эту ошибку простым способом нельзя, но, по крайней мере, она маловероятна.