unwind-protect
構文は、データ構造を一時的に不整合な状態に置くときに重要です。これはエラーやthrouのイベントにより、再びデータを整合された状態にすることができます(バッファー内容の変更だけに使用される他のクリーンアップ構成はアトミックな変更グループである。グループのアトミックな変更を参照)。
unwind-protect
は制御がbody-formを離れる場合に、cleanup-formsが評価されるという保証の下において、何が起こったかに関わらずbody-formを実行する。body-formは通常どおり完了するかもしれず、unwind-protect
の外側でthrow
の実行やエラーが発生するかもしれないが、cleanup-formsは評価される。
body-formが正常に終了したら、unwind-protect
はcleanup-formsを評価した後に、body-formの値をリターンする。body-formが終了しなかったら、unwind-protect
は通常の意味におけるような値はリターンしない。
unwind-protect
で保護されるのはbody-formだけである。cleanup-forms自体の任意のフォームが、(throw
またはエラーにより)非ローカルにexitすると、unwind-protect
は残りのフォームが評価されることを保証しない。cleanup-formsの中の1つが失敗することが問題となるようなら、そのフォームの周囲に他のunwind-protect
を配置して保護すること。
カレントでアクティブなunwind-protect
フォームの個数とローカル変数のバインドの個数の和にたいして、max-specpdl-size
の制限が課せられる(Local Variablesを参照)。
たとえば以下は一時的な使用のために不可視のバッファーを作成して、終了する前に確実にそのバッファーをkillする例です:
(let ((buffer (get-buffer-create " *temp*"))) (with-current-buffer buffer (unwind-protect body-form (kill-buffer buffer))))
(kill-buffer
(current-buffer))
のように記述して、変数buffer
を使用せずに同様のことを行えると思うかもしれません。しかし上の例は、別のバッファーにスイッチしたときにbody-formでエラーが発生した場合、より安全なのです(一時的なバッファーをkillするとき、そのバッファーがカレントとなることを確実にするために、かわりにbody-formの周囲にsave-current-buffer
を記述することもできる)。
Emacsには上のコードとおおよそ等しいコードに展開される、with-temp-buffer
という標準マクロが含まれます(Current
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 "Logged in") (error "Ftp login failed"))) (or win (and process (delete-process process)))))
この例には小さなバグがあります。ユーザーがquitするためにC-gとタイプすると、関数ftp-setup-buffer
のリターン後に即座にquitが発生しますが、それは変数process
がセットされる前なので、そのプロセスはkillされないでしょう。このバグを簡単に訂正する方法はありませんが、少なくともこれは非常に稀なことだと言えます。