← Back to all sparks
W

withr

DEVOPS
Velocity0.0

Run R code with temporarily modified global state that is safely restored afterwards

withr rebuilt defer() on base R's own on.exit(), and got much faster for it

state-managementtestingperformancebase-rmaintenance
Current state
withr provides the with_ and local_ functions that temporarily change global state and restore it on exit - the cleanup primitive under most of the tidyverse test suites. Version 3.0.0 reimplemented defer() as a thin wrapper over base::on.exit(), which the maintainers made possible by contributing the required arguments to R 3.5 itself. Everything since has been small correctness work.
Where it's heading
The arc here is a package pushing its own machinery down into base R and then living off the result. Once defer() became on.exit(), the remaining work is about where cleanup is allowed to happen - knitr documents, source() calls, the global environment, session end - and about restoring state correctly when an expression exits early. Releases are now infrequent and CRAN-driven.
Prediction
Expect continued low-volume releases fixing edge cases in where deferred handlers run; with the core delegated to base R, there is little left that would prompt a larger change.

Recent moves

  1. 1mo ago

    local_seed() actually preserves the seed now

    A correctness fix with teeth: local_seed() failed to preserve the seed, so a test that seeded locally would silently seed the tests that followed it. Suites that were relying on that accidental inheritance now need to seed each test on its own.

    View source ↗
  2. 1y ago

    local_language() no longer warns when set to C

    Setting the language to C is now a silent, cross-platform way to disable gettext translations. A one-line convenience in a package that ships very little these days.

    View source ↗
  3. 2y ago

    defer() becomes a thin wrapper over base::on.exit()

    ⚡ SPARK

    The release the rest of this timeline follows from: withr's core primitive stops being its own implementation and becomes a wrapper over base R, which is faster and finally orders correctly against handlers registered with on.exit() directly. The cost is a breaking change for source() with a local environment.

    View source ↗
  4. 4y ago

    Cleanup works inside Rmd and source(); with_() gains a getter

    Extends where deferred cleanup is allowed to run - inside knitr documents, where it fires when knitr exits, and inside source(). The get argument to with_() and local_() is the subtler change: it lets state be captured before it is set, so an early exit during the set still restores correctly.

    View source ↗
  5. 4y ago

    New maintainer; handlers run at session end

    Lionel Henry takes over maintenance, and handlers registered at top level in the global environment now run when the R session ends rather than never. Adds with_language() and local_language() for temporarily controlling translation language.

    View source ↗
  6. 5y ago

    local_options() can set NULL; with_seed() controls RNG kind

    A batch of API consistency work: local_options() can finally set an option to NULL as documented, local_tempfile()'s envir argument is deprecated to match the .local_envir used everywhere else, and with_seed() gains explicit RNG kind arguments.

    View source ↗