Skip to content
Error Handling
step 1/5

Reading — step 1 of 5

Learn

~2 min readMetaprogramming, Errors, Performance

R has multiple error mechanisms. Knowing them differentiates production code from notebooks.

Throw an error:

stop("something broke")
stop("x must be positive, got: ", x)

Throw a warning (non-fatal):

warning("deprecated, use new_function() instead")

Send a message (information, not warning):

message("loaded 1000 rows")

Catch with tryCatch:

result <- tryCatch({
    risky_operation()
}, error = function(e) {
    cat("caught:", conditionMessage(e), "\n")
    NA      # return value on error
}, warning = function(w) {
    cat("warning:", conditionMessage(w), "\n")
    -1
}, finally = {
    cat("cleanup\n")
})

The finally block runs whether or not error occurred. tryCatch is R's try/catch.

try() — older, simpler. Returns an "try-error" object on failure:

result <- try(risky(), silent = TRUE)
if (inherits(result, "try-error")) {
    cat("failed\n")
}

withCallingHandlers — like tryCatch but returns control to the throwing context (for warnings, you can keep going):

result <- withCallingHandlers({
    risky()
}, warning = function(w) {
    log_warning(w)
    invokeRestart("muffleWarning")    # suppress and continue
})

Custom condition classes for structured errors:

stop_validation <- function(msg) {
    structure(
        class = c("validation_error", "error", "condition"),
        list(message = msg, call = sys.call(-1))
    ) |> stop()
}

tryCatch({
    stop_validation("age must be positive")
}, validation_error = function(e) {
    cat("validation failed:", conditionMessage(e), "\n")
})

stopifnot() for invariants:

stopifnot(
    is.numeric(x),
    length(x) > 0,
    all(x >= 0)
)

If any condition fails, throws an error naming the failing one. Used heavily inside R itself.

assertthat package for richer messages:

library(assertthat)
assert_that(is.numeric(x), msg = "x must be numeric")

browser() for interactive debugging:

my_fn <- function(x) {
    browser()    # drops into interactive shell here
    x * 2
}

When you call my_fn(5), R pauses and gives you a debug REPL where you can inspect variables, step, continue.

Best practices:

  • Use stop() for things callers can't recover from (bug, missing data)
  • Use warning() for unusual situations callers should know about
  • Validate inputs at function start (stopifnot)
  • Custom condition classes for libraries — let users catch specific errors
  • Test error paths! Use expect_error() in testthat

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…