handler-bind
handler-bind Macro
Syntax:
handler-bind ({↓binding}*) {form}* → {result}*
binding::=(type handler)
Arguments and Values:
type—a type specifier .
handler—a form; evaluated to produce a handler-function.
handler-function—a designator for a function of one argument.
forms—an implicit progn.
results—the values returned by the forms.
Description:
Executes forms in a dynamic environment where the indicated handler bindings are in effect.
Each handler should evaluate to a handler-function, which is used to handle conditions of the given type during execution of the forms. This function should take a single argument, the condition being signaled.
If more than one handler binding is supplied, the handler bindings are searched sequentially from top to bottom in search of a match (by visual analogy with typecase). If an appropriate type is found, the associated handler is run in a dynamic environment where none of these handler bindings are visible (to avoid recursive errors). If the handler declines, the search continues for another handler .
If no appropriate handler is found, other handlers are sought from dynamically enclosing contours. If no handler is found outside, then signal returns or error enters the debugger.
Examples:
In the following code, if an unbound variable error is signaled in the body (and not handled by an intervening handler), the first function is called.
(handler-bind ((unbound-variable #’(lambda ...))
(error #’(lambda ...)))
...)
If any other kind of error is signaled, the second function is called. In either case, neither handler is active while executing the code in the associated function.
(defun trap-error-handler (condition)
(format \*error-output\* "~&~A~&" condition)
(throw ’trap-errors nil))
(defmacro trap-errors (&rest forms)
‘(catch ’trap-errors
(handler-bind ((error #’trap-error-handler))
,@forms)))
(list (trap-errors (signal "Foo.") 1)
(trap-errors (error "Bar.") 2)
(+ 1 2))
▷ Bar.
→ (1 NIL 3)
Note that “Foo.” is not printed because the condition made by signal is a simple condition, which is not of type error, so it doesn’t trigger the handler for error set up by trap-errors.
See Also:
Expanded Reference: handler-bind
Basic Condition Handling
Demonstrates how to establish a handler for a simple error condition. The handler is invoked, prints a message, and since it doesn't transfer control, the program terminates.
(handler-bind ((error #'(lambda (c)
(format t "Caught an error: ~a~%" c)
;; Without a transfer of control, the thread is terminated
)))
(error "This is an error condition."))
Caught an error: This is an error condition.
; Debugger entered on #<SIMPLE-ERROR "This is an error condition." {7007D435D3}>
Handling Multiple Conditions
Shows how to define handlers for different types of conditions. The first bound handler is invoked first. Even if it's less specific
If more than one handler binding is supplied, the handler bindings are searched sequentially from top to bottom in search of a match
(handler-bind ((error #'(lambda (c) (format t "Caught a general error: ~a~%" c)))
(simple-error #'(lambda (c) (format t "Caught a simple-error: ~a~%" c))))
(error 'simple-error :format-control "Another simple-error."))
Results in this:
Caught a general error: Another simple-error.
Caught a simple-error: Another simple-error.
; Debugger entered on #<SIMPLE-ERROR "Another simple-error." {7008596B73}>
Notice the order of the printouts is not based on what's more specific, rather on what was defined first top to bottom.
Declining to Handle a Condition
If a handler returns normally, it is said to have declined to handle the condition. The system then searches for the next available handler. In this example, two handlers for warning are established. When a warning is signaled, both handlers are run because they both return normally. They are run in top to bottom
(handler-bind ((warning #'(lambda (c) (format t "Top handler sees: ~a~%" c)))
(warning #'(lambda (c) (format t "Bottom handler sees: ~a~%" c))))
(warn "A warning is signaled."))
Top handler sees: A warning is signaled.
Bottom handler sees: A warning is signaled.
WARNING: A warning is signaled.
NIL
Transferring Control with a Restart
A handler can transfer control by invoking a restart. This bypasses other handlers and can allow the program to continue execution.
(defun my-function (x)
(restart-case
(if (zerop x)
(error "Cannot divide by zero.")
(/ 10 x))
(continue ()
:report "Return a default value of 1 instead."
1)))
(handler-bind ((error #'(lambda (c)
(declare (ignore c))
(invoke-restart 'continue))))
(print (my-function 0))
(print (my-function 2)))
1
5
5 (3 bits, #x5, #o5, #b101)
Notice that there was no printout in the REPL about an error or even a warning.
Note (since we don't have a way to color code these code blocks like in slime/sly): The first two lines are print outs, the third line is a value returned.
Capturing and Muffling Warnings
Demonstrates how to capture warnings and prevent them from being printed to *error-output*. The handler pushes the warning condition onto a list and then invokes the muffle-warning restart.
(let ((warnings-caught '()))
(handler-bind ((warning #'(lambda (c)
(push (princ-to-string c) warnings-caught)
(muffle-warning c))))
(warn "First warning.")
(warn "Second warning."))
(format t "Collected warnings: ~a~%" (reverse warnings-caught)))
Collected warnings: (First warning. Second warning.)
NIL