How and when should I use on.exit?

r, r-faq

Solution

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.

Packages that make use of `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.

Database connections

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"
)

File connections

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)

Temporary files

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")
}

Saving base graphics

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")

Setting base graphics options temporarily

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`

Setting global options temporarily

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`

Other examples

- Setting the working directory (`withr::with_dir`, `later::scope_dir`)

- Setting locale components (`withr::with_locale`)

- Setting environment variables (`withr::with_envvars`, `later::scope_env_var`)

- Setting library paths (`withr::with_libpaths`)

- Redirecting output with a sink

- Temporarily loading a package (`withr::with_package`, `withr::with_namespace`)

Problem

`on.exit` calls code when a function exits, but how and when should I use it?

Original source