5.6 中止 (Aborts)

你可以使用 return 在任何时候离开一个 block 。有时候我们想要做更极端的事,在数个函数调用里将控制权转移回来。要达成这件事,我们使用 catchthrow 。一个 catch 表达式接受一个标签(tag),标签可以是任何类型的对象,伴随着一个表达式主体:

  1. (defun super ()
  2. (catch 'abort
  3. (sub)
  4. (format t "We'll never see this.")))
  5. (defun sub ()
  6. (throw 'abort 99))

表达式依序求值,就像它们是在 progn 里一样。在这段代码里的任何地方,一个带有特定标签的 throw 会导致 catch 表达式直接返回:

  1. > (super)
  2. 99

一个带有给定标签的 throw ,为了要到达匹配标签的 catch ,会将控制权转移 (因此杀掉进程)给任何有标签的 catch 。如果没有一个 catch 符合欲匹配的标签时, throw 会产生一个错误。

调用 error 同时中断了执行,本来会将控制权转移到调用树(calling tree)的更高点,取而代之的是,它将控制权转移给 Lisp 错误处理器(error handler)。通常会导致调用一个中断循环(break loop)。以下是一个假定的 Common Lisp 实现可能会发生的事情:

  1. > (progn
  2. (error "Oops!")
  3. (format t "After the error."))
  4. Error: Oops!
  5. Options: :abort, :backtrace
  6. >>

译注:2 个 >> 显示进入中断循环了。

关于错误与状态的更多讯息,参见 14.6 小节以及附录 A。

有时候你想要防止代码被 throwerror 打断。借由使用 unwind-protect ,可以确保像是前述的中断,不会让你的程序停在不一致的状态。一个 unwind-protect 接受任何数量的实参,并返回第一个实参的值。然而即便是第一个实参的求值被打断时,剩下的表达式仍会被求值:

  1. > (setf x 1)
  2. 1
  3. > (catch 'abort
  4. (unwind-protect
  5. (throw 'abort 99)
  6. (setf x 2)))
  7. 99
  8. > x
  9. 2

在这里,即便 throw 将控制权交回监测的 catchunwind-protect 确保控制权移交时,第二个表达式有被求值。无论何时,一个确切的动作要伴随着某种清理或重置时, unwind-protect 可能会派上用场。在 121 页提到了一个例子。