Errors, warnings and conditions

R · Advanced · 12 min

Robust scripts and packages must fail clearly and clean up after themselves.

R's condition system has three main signals: stop() raises an error that aborts the call, warning() signals a warning that by default is printed after the call finishes, and message() emits an informational message (suppress with suppressMessages).

tryCatch(expr, error = function(e) ..., warning = function(w) ..., finally = ...) evaluates expr and, if a matching condition is signalled, exits to the handler, whose value becomes the result. conditionMessage(e) extracts the text. try(expr, silent = TRUE) is a lighter variant returning an object of class "try-error".

withCallingHandlers is different: its handlers run without unwinding, so after a warning handler returns, the code continues. Combine it with invokeRestart("muffleWarning") to log and silence warnings.

You can signal custom condition classes, which callers can catch precisely: stop(errorCondition("not found", class = "notFoundError", data = list(id = 7))). The rlang package's abort() makes this convenient.

Use on.exit(close(con), add = TRUE) inside functions to guarantee cleanup whether the function returns or errors. stopifnot() is a concise way to assert preconditions, and options(warn = 2) turns warnings into errors while debugging; traceback() and browser() help find where it happened.

Example

safe_log <- function(x) {
  tryCatch({
    if (!is.numeric(x)) stop(errorCondition("need a number", class = "typeError"))
    log(x)
  },
  typeError = function(e) { message("type problem: ", conditionMessage(e)); NA },
  warning = function(w) { message("warning caught: ", conditionMessage(w)); NaN })
}
print(safe_log(100))       #> [1] 4.60517
print(safe_log("a"))       #> type problem: need a number / [1] NA
print(safe_log(-1))        #> warning caught: NaNs produced / [1] NaN

res <- withCallingHandlers(
  { warning("first"); "kept going" },
  warning = function(w) { message("logged: ", conditionMessage(w))
                          invokeRestart("muffleWarning") })
print(res)                 #> logged: first / [1] "kept going"

f <- function() { on.exit(cat("cleanup\n")); stop("boom") }
try(f(), silent = TRUE)    #> cleanup

Remember

  • stop errors, warning warns, message informs
  • tryCatch exits to the handler; withCallingHandlers continues
  • Custom condition classes let callers catch precisely
  • on.exit(..., add = TRUE) guarantees cleanup

Watch out for

  • Catching every error with a broad handler and returning NA silently, hiding real bugs.

Check yourself

  1. A warning is handled with withCallingHandlers and the handler calls invokeRestart("muffleWarning"). What happens next?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: cran.r-project.org/doc/manuals/r-release/R-lang.html#Exception-handling

Builds on

Unlocks

Nothing builds on this yet.