SyntaxStudy
Sign Up
R Error Handling and Defensive Programming
R Beginner 1 min read

Error Handling and Defensive Programming

Writing robust R functions requires handling the three classes of conditions: messages (informational), warnings (potential problems), and errors (fatal problems). The tryCatch() function is the primary mechanism for catching and responding to conditions. You supply handlers for "error", "warning", and "message" as named arguments; if a condition of that class is signalled within the expression, the corresponding handler is called instead of propagating up the call stack. The withCallingHandlers() function is similar to tryCatch() but restarts execution after a non-error condition is handled, whereas tryCatch() unwinds the call stack. For errors, tryCatch() is the right tool because you generally cannot resume after an error. For warnings and messages you want to log but not suppress, withCallingHandlers() lets you record the condition and then allow normal execution to continue. Defensive programming practices in R include using stopifnot() for asserting preconditions, warning() to signal recoverable issues, and stop() to signal unrecoverable ones. The rlang package (part of the tidyverse) provides a richer condition system with custom condition classes that carry structured data, enabling more precise error handling and better error messages for package users.
Example
# stop() and warning() — signal conditions
safe_sqrt <- function(x) {
    if (!is.numeric(x)) stop("x must be numeric")
    if (any(x < 0))     warning("negative values will produce NaN")
    sqrt(x)
}
safe_sqrt(c(4, 9, 16))    # 2 3 4
safe_sqrt(c(4, -1))       # warning + NaN result

# tryCatch — catch errors
result <- tryCatch({
    log(-1)                 # produces NaN with a warning
    "success"
}, warning = function(w) {
    cat("Caught warning:", conditionMessage(w), "\n")
    NA
}, error = function(e) {
    cat("Caught error:  ", conditionMessage(e), "\n")
    NA
})
result   # NA (the warning handler returned NA)

# tryCatch around a real error
tryCatch(
    stop("something went wrong"),
    error = function(e) paste("Handled:", conditionMessage(e))
)

# withCallingHandlers — log and continue
withCallingHandlers({
    message("Step 1 complete")
    warning("Minor issue at step 2")
    42                   # final value
}, message = function(m) {
    cat("[MSG]", conditionMessage(m))
    invokeRestart("muffleMessage")
}, warning = function(w) {
    cat("[WARN]", conditionMessage(w), "\n")
    invokeRestart("muffleWarning")
})

# stopifnot — assert preconditions
divide <- function(a, b) {
    stopifnot(is.numeric(a), is.numeric(b), b != 0)
    a / b
}
divide(10, 2)    # 5
# divide(10, 0)  # Error: b != 0 is not TRUE