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 aborts all code currently run between with_handlers() and the point where the condition has been raised. with_handlers() passes the return value of the handler to its caller.

  • Calling handlers, which are executed from inside the signalling functions. Their return values are ignored, only their side effects matters. Valid side effects are writing a log message, or jumping out of the signalling context by invoking a restart or using return_from(). If the raised condition was an error, this interrupts the aborting process.

    If a calling handler returns normally, it effectively declines to handle the condition and other handlers on the stack (calling or exiting) are given a chance to handle the condition.

Handlers are exiting by default, use calling() to create a calling handler.

with_handlers(.expr, ...)

calling(handler)

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.

handler

A handler function that takes a condition as argument. This is passed to as_function() and can thus be a formula describing a lambda function.

Life cycle

exiting() is soft-deprecated as of rlang 0.4.0 because with_handlers() now treats handlers as exiting by default.

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 = handler)
#> [1] "handler return value"
# Calling 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 a calling 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"