Previous: , Up: Nonlocal Exits   [Contents][Index]


10.5.4 非ローカル脱出のクリーンアップ

unwind-protect構文は、データ構造を一時的に不整合な状態に置くときに重要です。これはエラーやthrouのイベントにより、再びデータを整合された状態にすることができます(バッファー内容の変更だけに使用される他のクリーンアップ構成はアトミックな変更グループである。Atomic Changesを参照)。

Special Form: unwind-protect body-form cleanup-forms…

unwind-protectは制御がbody-formを離れる場合に、cleanup-formsが評価されるという保証の下において、何が起こったかに関わらずbody-formを実行する。body-formは通常どおり完了するかもしれず、unwind-protectの外側でthrowの実行やエラーが発生するかもしれないが、cleanup-formsは評価される。

body-formが正常に終了したら、unwind-protectcleanup-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パッケージ由来の実例です。これはリモートマシンへの接続の確立を試みるために、プロセス(Processesを参照)を作成しています。関数ftp-loginは関数のライター(writer)が予想できないことによる多くの問題から非常に影響を受けるので、失敗イベントでプロセスの削除を保証するフォームで保護されています。そうしないと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されないでしょう。このバグを簡単に訂正する方法はありませんが、少なくともこれは非常に稀なことだと言えます。