R Error Handling — tryCatch and Debugging
Learning Objectives
By the end of this tutorial, you will be able to:
- Handle errors gracefully with
tryCatch()andtry() - Catch and manage warnings
- Create custom error conditions
- Use debugging tools like
browser(),debug(), andtraceback() - Write robust functions that handle edge cases
Error Handling Basics
try() — Simple Error Handling
# try() returns error message instead of stopping
result <- try(log("a"))
# Error in log("a") : non-numeric argument to mathematical function
class(result) # [1] "try-error"
# Check for error
if (inherits(result, "try-error")) {
cat("An error occurred\n")
}
# With silent = TRUE
result <- try(log("a"), silent = TRUE)
tryCatch() — Advanced Error Handling
# tryCatch with different handlers
result <- tryCatch(
{
log("a")
},
error = function(e) {
cat("Error:", conditionMessage(e), "\n")
NA
},
warning = function(w) {
cat("Warning:", conditionMessage(w), "\n")
NULL
}
)
# Error: non-numeric argument to mathematical function
# Handle specific error types
result <- tryCatch(
{
x <- 1
y <- "2"
x + y
},
error = function(e) {
if (grepl("non-numeric", conditionMessage(e))) {
cat("Type error caught\n")
NA
} else {
stop(e) # Re-throw unknown errors
}
}
)
Warning Handling
# Catch warnings
result <- withCallingHandlers(
{
x <- log(-1) # Produces warning
x
},
warning = function(w) {
cat("Warning caught:", conditionMessage(w), "\n")
invokeRestart("muffleWarning") # Suppress warning
}
)
# Suppress warnings
result <- suppressWarnings(log(-1))
# withCallingHandlers vs tryCatch
# withCallingHandlers: continues after handler
# tryCatch: exits the expression after handler
Custom Error Conditions
# Create custom error
validate_age <- function(age) {
if (!is.numeric(age)) {
stop("Age must be numeric", call. = FALSE)
}
if (age < 0 || age > 150) {
stop(paste("Age must be between 0 and 150, got:", age), call. = FALSE)
}
age
}
# Custom error class
invalid_input <- function(message) {
structure(
class = c("invalid_input", "error", "condition"),
list(message = message)
)
}
safe_divide <- function(a, b) {
if (b == 0) {
stop(invalid_input("Cannot divide by zero"))
}
a / b
}
# Handle custom errors
tryCatch(
safe_divide(10, 0),
invalid_input = function(e) {
cat("Invalid input:", e$message, "\n")
NA
}
)
Debugging Tools
browser() — Interactive Debugging
my_function <- function(x) {
browser() # Execution stops here
y <- x^2
z <- y + 1
return(z)
}
my_function(5)
# Called from: my_function(5)
# Browse[1]>
# Type n (next), c (continue), q (quit), s (step into)
debug() and undebug()
# Debug a function
debug(my_function)
my_function(5) # Enters debug mode
# Step through
undebug(my_function)
# Debug once
debugonce(my_function)
traceback() and options(error = traceback)
# Show call stack after error
f <- function(x) g(x)
g <- function(x) h(x)
h <- function(x) x + "error"
tryCatch(f(1), error = function(e) traceback())
# Auto-show traceback on error
options(error = traceback)
Common Error Patterns
# Validate input
safe_mean <- function(x, na.rm = FALSE) {
if (!is.numeric(x)) {
stop("x must be numeric")
}
if (length(x) == 0) {
stop("x must not be empty")
}
mean(x, na.rm = na.rm)
}
# Handle missing values
safe_log <- function(x) {
if (any(x <= 0)) {
warning("Non-positive values replaced with NA")
x[x <= 0] <- NA
}
log(x)
}
# Retry pattern
retry <- function(expr, max_attempts = 3) {
for (i in 1:max_attempts) {
result <- tryCatch(
expr,
error = function(e) NULL
)
if (!is.null(result)) return(result)
cat("Attempt", i, "failed, retrying...\n")
}
stop("All attempts failed")
}
Practice Exercises
Exercise 1: Safe Calculator
Write a calculator function that handles division by zero, non-numeric inputs, and returns meaningful error messages.
Solution
safe_calculator <- function(a, b, op) {
if (!is.numeric(a) || !is.numeric(b)) {
stop("Inputs must be numeric")
}
switch(op,
add = a + b,
subtract = a - b,
multiply = a * b,
divide = {
if (b == 0) {
stop("Cannot divide by zero")
}
a / b
},
stop(paste("Unknown operation:", op))
)
}
safe_calculator(10, 5, "divide") # [1] 2
safe_calculator(10, 0, "divide") # Error: Cannot divide by zero
safe_calculator("a", 5, "add") # Error: Inputs must be numeric
Key Takeaways
try()is simple — returns try-error on failuretryCatch()is flexible — separate handlers for errors, warnings, messageswithCallingHandlers()handles warnings without stopping- Custom conditions let you create domain-specific errors
browser()pauses execution for interactive debuggingdebug()steps through a function line by linetraceback()shows the call stack after an error- Always validate inputs at the start of functions
Next: Learn about R Packages — extending R's functionality.