#> starting f()
#> starting g()
#> Error in g(): This is an error!
An error might occur due to events outside of your program—a missing file, a full disk, a crashed server, etc.
“Errors aren’t caused by bugs, but neglecting to handle an error is almost certainly a bug.” —Seibel (2003)
Conditions help us maintain the illusion of “black box” functions.
Function authors signal conditions
Function users handle conditions
stop() to print error messagesstop(...) can accept zero or more objects
character and pasted together with no separatorPass call. = FALSE to prevent the function that raised the error from being added to the message
rlang::abort() signals errors with a different interface
#> 4: stop("This is an error!") at #2
#> 2: g() at #3
#> 1: f()
This is one reason to prefer stop(..., call. = FALSE)
rlang::last_trace() has prettier printing#> <error/rlang_error>
#> Error in `g()`:
#> ! This is an error!
#> ---
#> Backtrace:
#> ▆
#> 1. └─global f()
#> 2. └─global g()
#> Run rlang::last_trace(drop = FALSE) to see 1 hidden frame.
#> finishing g()
#> finishing f()
#> Warning messages:
#> 1: In f() : F1
#> 2: In g() : G1
#> 3: In f() : F2
utils::txtProgressBar() and otherstry()try() with assignment can support default values in case of failure#> message 1
#> message 2
#> [1] 42
#> [1] 42
#> Warning messages:
#> 1: In chatty_function() : warning 1
#> 2: In chatty_function() : warning 2
These can be changed through exiting and calling handlers
tryCatch() specifies or modifies exiting handlers#> Reconstituting 'x'
#> [1] 23
tryCatch(
message = function(cnd) {
cat("Caught a message condition:", conditionMessage(cnd))
"The return value of the message handler"
},
{
message("This is a message")
cat("This code won't be run inside 'tryCatch()' if messages are caught\n")
"The return value of the original expression"
},
finally = {
cat("The code in 'finally' is always run\n")
}
)#> Caught a message condition: This is a message
#> The code in 'finally' is always run
#> [1] "The return value of the message handler"
withCallingHandlers() specifies or modifies calling handlerswithCallingHandlers(
message = function(cnd) {
cat("Caught a message condition:", conditionMessage(cnd))
"The return value of the message handler is ignored"
},
{
message("This is a message")
cat("This code should run\n")
message("This is another message")
"The return value of the original expression"
}
# No finally option
)#> Caught a message condition: This is a message
#> This code should run
#> Caught a message condition: This is another message
#> [1] "The return value of the original expression"
tryCatch()rlang::cnd_muffle() will stop the propagation to calling handlers “higher up”#> ▆
#> 1. ├─base::withCallingHandlers(...)
#> 2. ├─global f()
#> 3. │ └─global g()
#> 4. │ └─base::message("hello")
#> 5. │ ├─base::withRestarts(...)
#> 6. │ │ └─base (local) withOneRestart(expr, restarts[[1L]])
#> 7. │ │ └─base (local) doWithOneRestart(return(expr), restart)
#> 8. │ └─base::signalCondition(cond)
#> 9. └─global `<fn>`(`<smplMssg>`)
#> 10. └─lobstr::cst()
tryCatch()#> ▆
#> 1. └─base::tryCatch(f(), message = function(cnd) lobstr::cst())
#> 2. └─base (local) tryCatchList(expr, classes, parentenv, handlers)
#> 3. └─base (local) tryCatchOne(expr, names, parentenv, handlers[[1L]])
#> 4. └─value[[3L]](cond)
#> 5. └─lobstr::cst()
#> List of 2
#> $ message: chr "An error!"
#> $ call : language force(expr)
#> - attr(*, "class")= chr [1:3] "simpleError" "error" "condition"
message can be accessed with conditionMessage(cnd)call is not generally usefulclass attribute will be explained alongside S3#> List of 5
#> $ message: chr "An error!"
#> $ trace :Classes 'rlang_trace', 'rlib_trace', 'tbl' and 'data.frame': 8 obs. of 6 variables:
#> ..$ call :List of 8
#> .. ..$ : language rlang::catch_cnd(rlang::abort("An error!"))
#> .. ..$ : language eval_bare(rlang::expr(tryCatch(!!!handlers, { force(expr) ...
#> .. ..$ : language tryCatch(condition = `<fn>`, { force(expr) ...
#> .. ..$ : language tryCatchList(expr, classes, parentenv, handlers)
#> .. ..$ : language tryCatchOne(expr, names, parentenv, handlers[[1L]])
#> .. ..$ : language doTryCatch(return(expr), name, parentenv, handler)
#> .. ..$ : language force(expr)
#> .. ..$ : language rlang::abort("An error!")
#> ..$ parent : int [1:8] 0 1 1 3 4 5 1 0
#> ..$ visible : logi [1:8] FALSE FALSE FALSE FALSE FALSE FALSE ...
#> ..$ namespace : chr [1:8] "rlang" "rlang" "base" "base" ...
#> ..$ scope : chr [1:8] "::" "::" "::" "local" ...
#> ..$ error_frame: logi [1:8] FALSE FALSE FALSE FALSE FALSE FALSE ...
#> ..- attr(*, "version")= int 2
#> $ parent : NULL
#> $ rlang :List of 1
#> ..$ inherit: logi TRUE
#> $ call : NULL
#> - attr(*, "class")= chr [1:3] "rlang_error" "error" "condition"
rlang::abort() can compose and throw custom conditionsWe create a function that will signal an error condition
#> Error in `abort_missing_file()`:
#> ! Path `blah.csv` not found
This makes it easy to format messages
See the sub-section in the book for more excellent examples.