UNWIND-PROTECT is one of Common Lisp's "Special Operators". A special operator is basically something that comes bundled with the language as a form of primitive functionality, and which provides support for higher-level functionality. In fact, basic macros that we use on a daily basis such as do, dolist, dotimes can all be (are arguably are) implemented using low-level special operators such as BLOCK, RETURN-FROM, TAGBODY, and GO. For instance, suppose we have the following code that simply enumerates numbers from 1 to 10:

(dotimes (i 10) (format t "~a~%" (+ i 1)))

is expanded (using the slime shortcut, C-C M-m to expand macro forms (the actual command used is 'slime-macroexpand-all') at first to:

(DO ((I 0 (1+ I))) ((>= I 10) NIL) (DECLARE (TYPE UNSIGNED-BYTE I)) (FORMAT T "~a~%" (+ I 1)))

Ah! So as suspected, dotimes is implemented using the lower-level 'do' macro (at least in the Lisp implementation that i am using – SBCL). Now if we invoke C-c M-m again on this code, we finally get:

(BLOCK NIL (LET ((I 0)) (DECLARE (TYPE UNSIGNED-BYTE I)) (TAGBODY (GO #:G809) #:G808 (TAGBODY (FORMAT T "~a~%" (+ I 1))) (PSETQ I (1+ I)) #:G809 (UNLESS (>= I 10) (GO #:G808)) (RETURN-FROM NIL (PROGN NIL)))))

Interesting! This shows that the dotimes macro code ultimately expands into a block with no name (nil). It then binds the variable i (which is declared to be an unsigned byte) using the LET special operator, and then the whole looping code is actually bound within a TAGBODY. Of course, the RETURN-FROM is effected for the outer block and not for the TAGBODY.

Now, UNWIND-PROTECT is a special operator that is used to provide some form of cleanup facility for code that can potentially perform non-local exits, in which case crucial resources could potentially be leaked. The general form of this special operator is:

(unwind-protect protected-expression cleanup-form*)

The only caveat is that if there are some non-local exits in the cleanup form(s), then unwind-protect offers no protection from those scenarios. Despite this shortcoming, this feature is extremely powerful and useful. This is the basis on which a lot of WITH-* macros are built, for instance, the WITH-OPEN-FILE macro which helps ensure that file handles (stream handles) are closed no matter what happens within the actual body of code that runs on that file stream. For instance, we might have a small function to copy a binary file as follows:

(defun copy-binary-file () (let* ((source-file (progn (princ "Enter source file name: ") (read-line))) (dest-file (make-pathname :name "copy" :defaults source-file))) (with-open-file (in source-file :direction :input :if-does-not-exist :error :element-type '(unsigned-byte 8)) (with-open-file (out dest-file :direction :output :if-does-not-exist :create :if-exists :overwrite :element-type '(unsigned-byte 8)) (loop for byte = (read-byte in nil nil) while byte do (write-byte byte out))))))

This function simply copies a user-supplied binary file to a backup file named "copy.". All attributes of the backup file apart from its name are picked up from the source file itself.

Let us just examine (if we are feeling particularly masochistic) how the expanded code looks like:

(PROGN (EVAL-WHEN (:COMPILE-TOPLEVEL) (SB-C:%COMPILER-DEFUN 'COPY-BINARY-FILE 'NIL T)) (SB-IMPL::%DEFUN 'COPY-BINARY-FILE (SB-INT:NAMED-LAMBDA COPY-BINARY-FILE NIL (BLOCK COPY-BINARY-FILE (LET* ((SOURCE-FILE (PROGN (PRINC "Enter source file name: ") (READ-LINE))) (DEST-FILE (MA