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_protectso Rust destructors run if R longjmps. - Inside a
with_r_unwind_protectbody it is safe to use*_uncheckedvariants of the R FFI — see thecrate::sysmodule 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 andcrate::worker::with_r_threadbodies).
§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
crate::worker::with_r_thread— routes a closure to R’s main thread.crate::ffi_guard— unified panic-catching trampoline that consumeswith_r_unwind_protect_sourcedfor ALTREPRUnwindmode.crate::error_value/crate::condition— panic → R condition transport.
Statics§
- R_
CONTINUATION_ 🔒TOKEN - Global continuation token for R_UnwindProtect.
Functions§
- drain_
log_ 🔒queue_ if_ available - Drain the cross-thread log queue if the
logfeature 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 evaluatingstop(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 viaR_ContinueUnwindon R longjmp. - stop_
sym 🔒 - Cached
stopsymbol (permanently interned viaRf_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 customPanicSource.