purrr::safely() / purrr::possibly() / purrr::quietly()

safely(.f, otherwise = NULL, quiet = TRUE)
possibly(.f, otherwise = NULL, quiet = TRUE)
quietly(.f)
Returns: list · Updated March 13, 2026 · Tidyverse
purrr error-handling functional-programming

The safely(), possibly(), and quietly() functions from purrr wrap existing functions to handle errors, warnings, and messages gracefully. These wrappers are essential when applying functions across many elements—such as rows of a data frame or elements of a list—where a single failure would abort the entire operation.

Syntax

library(purrr)

# safely: captures both errors and results
safely(.f, otherwise = NULL, quiet = TRUE)

# possibly: returns a default value on error
possibly(.f, otherwise = NULL, quiet = TRUE)

# quietly: captures warnings and messages but lets errors fail
quietly(.f)

Parameters

ParameterTypeDefaultDescription
.ffunctionThe function to wrap
otherwiseanyNULLValue to return on error (possibly only)
quietlogicalTRUESuppress warnings and messages

Examples

Basic usage with safely()

safely() returns a list with two elements: result and error. If the function succeeds, result contains the output and error is NULL. If it fails, result is NULL and error contains the error condition.

library(purrr)

# Wrap a function that sometimes fails
safe_divide <- safely(function(x, y) {
  if (y == 0) stop("Division by zero")
  x / y
})

# Successful call
safe_divide(10, 2)
# $result
# [1] 5
# 
# $error
# NULL

# Failed call
safe_divide(10, 0)
# $result
# NULL
# 
# $error
# <simpleError: Division by zero>

Using possibly() for default values

possibly() is a convenience wrapper that returns a specified default value instead of an error.

library(purrr)

# Return NA on failure
safe_log <- possibly(log, otherwise = NA_real_)

# Works fine
safe_log(10)
# [1] 2.302585

# Returns NA instead of error
safe_log(-10)
# [1] NA

Combining with map()

These wrappers shine when used with map() to process multiple items where some might fail.

library(purrr)

# Vector of values to process
values <- c(100, 25, -5, 16, 0)

# Apply safely across all values
results <- map(values, safely(function(x) {
  if (x <= 0) stop("Must be positive")
  log(x)
}))

# Check which succeeded
succeeded <- map_lgl(results, ~is.null(.x$error))
succeeded
# [1]  TRUE  TRUE FALSE  TRUE FALSE

# Extract successful results
map_dbl(results[succeeded], "result")
# [1] 4.605170 3.218876 2.772589

Using quietly() to capture output

quietly() captures warnings and messages without suppressing errors.

library(purrr)

quiet_sqrt <- quietly(sqrt)

# Normal call with warning
quiet_sqrt(-4)
# $result
# [1] NaN
# 
# $warnings
# [1] "NaNs produced"
# 
# $messages
# character(0)
# 
# $result
# [1] NaN

Common Patterns

Error-tolerant data import

library(purrr)
library(readr)

# Try multiple file paths, continue on failure
file_paths <- c("data1.csv", "data2.csv", "data3.csv")

safe_read <- possibly(read_csv, otherwise = NULL)

data_list <- map(file_paths, safe_read)
valid_data <- keep(data_list, ~!is.null(.x))

Conditional processing pipelines

library(purrr)
library(dplyr)

# Process each row with potential failures
results <- df %>%
  mutate(safe_result = map2(col_a, col_b, ~possibly(
    function(x, y) compute_something(x, y),
    otherwise = NA
  )(.x, .y)))

See Also