<- read.csv(here::here("data", "fruit.csv"), strip.white = TRUE)
dat source(here::here("R", "fruit_avg.R"))
fruit_avg(dat, pattern = "black")
#> Found fruits!
#> Error in rowMeans(mini_dat): 'x' must be an array of at least two dimensions
Day 13: rlang::entrace
options(error = rlang::entrace)
and rlang::last_trace()
.
Much of rlang (Henry and Wickham 2022) is developer facing, including its tools for capturing and displaying error. What I’m about to show you here is an exception. Enabling rlang::entrace()
and using rlang::last_trace(
) basically gives you a simplified backtrace (the thing that displays the call stack) for errors, making it easier for any user to see where they went wrong.
Don’t believe me? Jenny Bryan said it in her 2020 talk on debugging, Object of type ‘closure’ is not subsettable:
One of the things [rlang] also offers is some new takes at how to present the call stack, and that’s the one part of that area of rlang that might be relevant to just about anyone (Bryan 2020). (Emphasis added)
By setting options(error = rlang::entrace)
in, say, your .Rprofile or in a Quarto or R Markdown document you will promote base R errors to rlang errors. This accomplishes two things (the descriptions of which I’ll lift straight from the rlang docs):
It saves the base error as an rlang object so you can call
last_error()
to print the backtrace or inspect its data.It prints the backtrace for the current error according to the
rlang_backtrace_on_error
option.
This is a case where it’s much easier to show than tell. Let’s look at the difference in call stack in the fruits debugging demo from Jenny’s talk.1 Below, I’m sourcing the code and getting an error (just as I normally would in an interactive R session).
1 I’m not going through the code here, since it’s tricky to knit errors nicely, and also this is about form as opposed to content.
Because the printing and handling of errors in R Markdown/Quarto is different to how it normally works in R, I’m just going to show you the difference in outputs with rlang::last_trace()
and base::traceback()
as they occur when I run them after this error in my console.
I have entrace
enabled globally, so the output for base::traceback()
is the following:
::traceback()
base#> 3: stop("'x' must be an array of at least two dimensions")
#> 2: rowMeans(mini_dat) at fruit_avg.R#8
#> 1: fruit_avg(dat, pattern = "black")
If I run rlang::last_trace()
, I get:
::last_trace()
rlang#> <error/rlang_error>
#> Error:
#> ! 'x' must be an array of at least two dimensions
#> ---
#> Backtrace:
#> ▆
#> 1. └─global fruit_avg(dat, pattern = "black")
#> 2. └─base::rowMeans(mini_dat) at 2022-tidyverse-advent/R/fruit_avg.R:8:2
This repeats the error message, and then prints a simplified tree of calls that led to my error. It tells me where my error was, and, in RStudio, this is an interactive link to the line in the appropriate file. The console output also uses colors and symbols from the cli package to highlight the different parts (see the screenshot below).
Personally, I almost always want to use rlang::last_trace()
. So, in addition to enabling options(error = rlang::entrace)
in my profile, I also override the native traceback()
function with rlang::last_trace()
.
Learn more
The aforementioned call stack section from Jenny Bryan’s debugging talk is a great way to learn about the (often mysterious) call stack, and what exactly you’re being shown when you run a traceback (or backtrace—they mean the same thing).
If you are a developer, or just want to learn more about rlang’s tools for signalling and displaying errors, there are several Conditions vignettes in the rlang documentation: