on.exit
on.exit() registers an expression that R evaluates when the enclosing function exits, regardless of whether it returns normally, hits an early return(), or throws an error. This makes it the standard way to handle cleanup in R functions — you register what needs to happen once, and R handles it on the way out.
Signature
on.exit(expr, add = FALSE, which = NULL)
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
expr | expression | — | An expression evaluated when the current function exits |
add | logical | FALSE | If FALSE, replaces any existing exit expression; if TRUE, appends to the list |
which | integer | NULL | R ≥ 3.5.0 only. Specifies which existing exit handler to remove |
The return value is an integer representing the exit handler, returned invisibly. You rarely need to use it.
Basic Usage
The simplest pattern captures and restores some state:
quiet_mode <- function(expr) {
old_scipen <- options("scipen")$scipen
on.exit(options(scipen = old_scipen), add = TRUE)
options(scipen = 999)
eval(substitute(expr))
}
quiet_mode(print(1e10))
# [1] 10000000000
options("scipen")$scipen
# [1] 0
By default, on.exit() replaces any previously registered expression:
f <- function() {
on.exit(cat("first\n"))
on.exit(cat("second\n"))
cat("body\n")
}
f()
# body
# second
The first handler is gone. Use add = TRUE to append instead:
f <- function() {
on.exit(cat("first\n"), add = TRUE)
on.exit(cat("second\n"), add = TRUE)
cat("body\n")
}
f()
# body
# second
# first
Exit handlers run in reverse order of registration — last in, first out (LIFO).
Lazy Evaluation
The registered expression is stored as a promise. Variables are looked up at exit time, not registration time:
x <- 10
f <- function() {
on.exit(print(x))
x <- 20
cat("body, x =", x, "\n")
}
f()
# body, x = 20
# [1] 20
If you need to capture a value at registration time, force evaluation explicitly:
f <- function() {
snapshot <- x # force evaluation now
on.exit(print(snapshot))
x <- 20
}
f()
# [1] 10
Common Use Cases
Reset options()
wider_print <- function(df) {
old_digits <- options("digits")$digits
on.exit(options(digits = old_digits), add = TRUE)
options(digits = 15)
print(df)
}
wider_print(head(mtcars))
# mpg cyl disp hp drat wt qsec vs am gear carb
# Mazda RX4 21.0 6 160.0 110 3.90 2.620 16.46 0 1 4 4
# Mazda RX4 Datsun 21.0 6 160.0 110 3.90 2.875 17.02 0 1 4 4
options("digits")$digits
# [1] 7
Close connections
count_lines <- function(path) {
conn <- file(path, "r")
on.exit(close(conn), add = TRUE)
length(readLines(conn))
}
count_lines("/etc/hosts")
# [1] 10
# connection is closed automatically
Restore graphical parameters
plot_wide <- function() {
old_mar <- par("mar")
on.exit(par(mar = old_mar), add = TRUE)
par(mar = c(2, 2, 2, 2))
plot(1:10)
# margins restored on exit
}
Clean up temp files
process_file <- function(url) {
tmp <- tempfile()
on.exit(unlink(tmp), add = TRUE)
download.file(url, tmp, quiet = TRUE)
read.csv(tmp)
# temp file deleted regardless of success or failure
}
Interaction with tryCatch
on.exit() runs inside tryCatch() — it fires whether the body succeeds or throws an error:
f <- function() {
on.exit(cat("cleanup\n"))
tryCatch({
cat("trying\n")
stop("oops")
}, error = function(e) {
cat("caught:", conditionMessage(e), "\n")
})
cat("after tryCatch\n")
}
f()
# trying
# caught: oops
# cleanup
# after tryCatch
R 4.0.0 added a finally argument to tryCatch() that serves a similar purpose:
f <- function() {
tryCatch({
cat("body\n")
}, finally = {
cat("cleanup via finally\n")
})
}
f()
# body
# cleanup via finally
Both on.exit() and finally handle cleanup, but on.exit() is idiomatic for per-function state management while finally is useful for block-level operations.
which Argument (R ≥ 3.5.0)
The which argument lets you remove specific exit handlers by their index:
f <- function() {
h1 <- on.exit(cat("first\n"), add = TRUE)
h2 <- on.exit(cat("second\n"), add = TRUE)
on.exit(which = h1)
cat("body\n")
}
f()
# body
# second
See Also
tryCatch()— R’s full error handling mechanismcat()— output objects to the console (often used in exit handlers)