Condition handlers are functions established on the evaluation stack (see ctxt_stack()) that are called by R when a condition is signalled (see cnd_signal() and abort() for two common signal functions). They come in two types: exiting handlers, which jump out of the signalling context and are transferred to with_handlers() before being executed. And calling handlers, which are executed within the signal functions.

with_handlers(.expr, ...)

Arguments

.expr

An expression to execute in a context where new handlers are established. The underscored version takes a quoted expression or a quoted formula.

...

Named handlers. These should be functions of one argument. These handlers are treated as exiting by default. Use calling() to specify a calling handler. These dots support tidy dots features and are passed to as_function() to enable the formula shortcut for lambda functions.

Details

An exiting handler is taking charge of the condition. No other handler on the stack gets a chance to handle the condition. The handler is executed and with_handlers() returns the return value of that handler. On the other hand, in place handlers do not necessarily take charge. If they return normally, they decline to handle the condition, and R looks for other handlers established on the evaluation stack. Only by jumping to an earlier call frame can a calling handler take charge of the condition and stop the signalling process. Sometimes, a muffling restart has been established for the purpose of jumping out of the signalling function but not out of the context where the condition was signalled, which allows execution to resume normally. See cnd_muffle() and the mufflable argument of cnd_signal().

Exiting handlers are established first by with_handlers(), and in place handlers are installed in second place. The latter handlers thus take precedence over the former.

See also

Examples

# Signal a condition with signal(): fn <- function() { g() cat("called?\n") "fn() return value" } g <- function() { h() cat("called?\n") } h <- function() { signal("A foobar condition occurred", "foo") cat("called?\n") } # Exiting handlers jump to with_handlers() before being # executed. Their return value is handed over: handler <- function(c) "handler return value" with_handlers(fn(), foo = exiting(handler))
#> [1] "handler return value"
# Handlers are exiting by default so you can omit the adjective: with_handlers(fn(), foo = handler)
#> [1] "handler return value"
# In place handlers are called in turn and their return value is # ignored. Returning just means they are declining to take charge of # the condition. However, they can produce side-effects such as # displaying a message: some_handler <- function(c) cat("some handler!\n") other_handler <- function(c) cat("other handler!\n") with_handlers(fn(), foo = calling(some_handler), foo = calling(other_handler))
#> some handler! #> other handler! #> called? #> called? #> called?
#> [1] "fn() return value"
# If an in place handler jumps to an earlier context, it takes # charge of the condition and no other handler gets a chance to # deal with it. The canonical way of transferring control is by # jumping to a restart. See with_restarts() and restarting() # documentation for more on this: exiting_handler <- function(c) rst_jump("rst_foo") fn2 <- function() { with_restarts(g(), rst_foo = function() "restart value") } with_handlers(fn2(), foo = calling(exiting_handler), foo = calling(other_handler))
#> [1] "restart value"