Skip to main content

Module unwind_protect

Module unwind_protect 

Source
Expand description

Safe API for R’s R_UnwindProtect

This module provides with_r_unwind_protect for handling R errors with Rust cleanup. It automatically runs Rust destructors when R errors occur.

Important: R uses longjmp for error handling, which normally bypasses Rust destructors. Use this API to ensure cleanup happens even when R errors occur.

§When to reach for this

  • Calling R APIs that can error from a body you wrote yourself (custom ALTREP, custom connection trampoline, hand-rolled FFI shim). Wrap the R-calling section in with_r_unwind_protect so Rust destructors run if R longjmps.
  • Inside a with_r_unwind_protect body it is safe to use *_unchecked variants of the R FFI — see the crate::sys module doc. The lint MXL301 recognises this as one of the three contexts where bypassing the main-thread assertion is valid (the other two being ALTREP callbacks and crate::worker::with_r_thread bodies).

§You probably don’t need this from a #[miniextendr] body

The proc-macro already wraps every function and method in a guard that converts panics into the tagged-condition transport (crate::error_value). Returning Result::Err, Option::None, or calling panic!() / crate::error! / crate::warning! / crate::message! is the idiomatic path. Direct with_r_unwind_protect use inside that body is almost always wrong — you’d be nesting an R_UnwindProtect inside another R_UnwindProtect, paying the longjmp-leak cost twice (see “Leaks” below).

§Don’t use Rf_error

Rf_error and Rf_errorcall longjmp directly, skipping every Rust destructor on the stack. The lint MXL300 forbids them in user code. Panic instead (or call crate::error!) and the framework raises the corresponding R condition for you.

§Leaks

On the R longjmp path (when R unwinds out of the protected body), with_r_unwind_protect leaks ~8 bytes (an RErrorMarker + Box header) because the cleanup handler can’t reclaim them via Box::from_raw. Regular Rust panics from inside the body don’t leak. This is the cost MXL300 is buying off: every direct Rf_error() would incur the same leak with no observability.

§Log drain

Every call to with_r_unwind_protect (and its variants) drains the cross-thread log queue via the crate-private drain_log_queue_if_available helper before returning or re-raising an R error. This ensures that records buffered by worker threads are flushed to R’s console on every FFI exit — including error paths.

§Cross references

Statics§

R_CONTINUATION_TOKEN 🔒
Global continuation token for R_UnwindProtect.

Functions§

drain_log_queue_if_available 🔒
Drain the cross-thread log queue if the log feature is enabled.
get_continuation_token 🔒
Get or create the global continuation token.
panic_payload_to_string
Extract a message from a panic payload.
raise_rust_condition_via_stop 🔒
Raise an R condition with rust_* class layering by evaluating stop(structure(list(message = msg, call = call), class = c(...))).
run_r_unwind_protect 🔒
Core R_UnwindProtect wrapper. Returns Ok(result) on success, Err(payload) on Rust panic, or diverges via R_ContinueUnwind on R longjmp.
stop_sym 🔒
Cached stop symbol (permanently interned via Rf_install).
with_r_unwind_protect
Run a closure under R_UnwindProtect, returning a tagged condition SEXP on Rust panics instead of raising an R error.
with_r_unwind_protect_or_raise
Execute a closure with R unwind protection, raising any Rust panic as an R error via Rf_eval(stop(structure(...))).
with_r_unwind_protect_shim
Like with_r_unwind_protect, but tailored for trait-ABI vtable shims.
with_r_unwind_protect_sourced 🔒
Like with_r_unwind_protect_or_raise, but reports panics with a custom PanicSource.