on.exit
calls code when a function exits, but how and when should I use it?
The advantage of on.exit
is that is gets called when the function exits, regardless of whether an error was thrown. This means that its main use is for cleaning up after risky behaviour. Risky, in this context, usually means accessing resources outside of R (that consequently cannot be guaranteed to work). Common examples include connecting to databases or file (where the connection must be closed when you are finished, even if there was an error), or saving a plot to a file (where the graphics device must be closed afterwards).
You can also use on.exit
for low-risk behaviour with a side effect, such as setting a working directory.
In general, you should set add = TRUE
in on.exit()
. See https://adv-r.hadley.nz/functions.html?q=on.exit#on-exit.
on.exit
The withr
package contains many with_*
functions that change a setting, run some code, then change the setting back. These functions also appear in the devtools
package.
An alternate syntax is found in the later
package where defer
is a convenience wrapper to on.exit
, and scope_*
functions work like the with_*
functions in the previously mentioned packages.
In this example, sqlite_get_query
connects to an sqlite database, ensuring
that the connection always gets closed after the query has run. The cookies
database requires that you have firefox installed on your machine, and you may
need to adjust the path to find the cookies file.
library(RSQLite)
sqlite_get_query <- function(db, sql)
{
conn <- dbConnect(RSQLite::SQLite(), db)
on.exit(dbDisconnect(conn), add = TRUE)
dbGetQuery(conn, sql)
}
cookies <- dir(
file.path(Sys.getenv("APPDATA"), "Mozilla", "Firefox"),
recursive = TRUE,
pattern = "cookies.sqlite$",
full.names = TRUE
)[1]
sqlite_get_query(
cookies,
"SELECT `baseDomain`, `name`, `value` FROM moz_cookies LIMIT 20"
)
In this example, read_chars
wraps readChars
, ensuring that the connection
to the file is always closed after reading is finished.
read_chars <- function(file_name)
{
conn <- file(file_name, "r")
on.exit(close(conn), add = TRUE)
readChar(conn, file.info(file_name)$size)
}
tmp <- tempfile()
cat(letters, file = tmp, sep = "")
read_chars(tmp)
The following example adapted from CodeDepends uses a temporary file to save the session history. This temporary file is not needed once the function returns so it is removed.
history_lines <- function()
{
f <- tempfile()
on.exit(unlink(f), add = TRUE)
savehistory(f)
readLines(f, encoding = "UTF-8")
}
In this example, my_plot
is a function that creates a plot using base
graphics. save_base_plot
accepts a function and a file to save it to, using
on.exit
to ensure that the graphics device is always closed.
my_plot <- function()
{
with(cars, plot(speed, dist))
}
save_base_plot <- function(plot_fn, file)
{
png(file)
on.exit(dev.off(), add = TRUE)
plot_fn()
}
save_base_plot(my_plot, "testcars.png")
In this example, plot_with_big_margins
calls plot
, overriding the global mar
gin par
ameter, using on.exit
to reset it after the plot is completed.
plot_with_big_margins <- function(...)
{
old_pars <- par(mar = c(10, 9, 9, 7))
on.exit(par(old_pars), add = TRUE)
plot(...)
}
plot_with_big_margins(with(cars, speed, dist))
withr
/devtools
equivalent: with_par
In this example, create_data_frame
is a function that creates a data.frame
. create_data_frame
ensures that the created object doesn't contain explicit factors.
create_data_frame <- function(){
op <- options(stringsAsFactors = FALSE)
on.exit(options(op), add = TRUE)
data.frame(x=1:10)
}
withr
/devtools
equivalent: with_options
later
equivalent: scope_options
withr::with_dir
, later::scope_dir
)withr::with_locale
)withr::with_envvars
, later::scope_env_var
)withr::with_libpaths
)withr::with_package
, withr::with_namespace
)