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.
Structs§
- Boundary 🔒
- A miniextendr boundary that resumes R exits (
RUnwind): the innermost on its thread fromenteruntil it is dropped orleaves. - PreLocated
Panic 🔒 - Panic payload whose message is already final (location folded, or deliberately location-free): downstream folds must use it verbatim and must NOT append the current thread’s recorded panic location (#1245).
- RUnwind 🔒
- An R non-local exit that
call_unwindingcarries out of its callback as a Rust unwind: an error, a condition caught by an exiting handler set up outside the call (tryCatch(warning = )), a restart, an interrupt.
Constants§
- BOUNDARY 🔒
- The innermost
Boundaryon this thread;0outside any. - LAST_
BOUNDARY 🔒 - The last boundary id handed out on this thread.
Statics§
- ABANDONED_
OFF_ 🔒MAIN - Tokens of R exits dropped off R’s main thread, where
R_ReleaseObjectwould race R.call_unwindingreleases them on the main thread. - HAS_
ABANDONED_ 🔒OFF_ MAIN - Whether
ABANDONED_OFF_MAINmay hold a token: the evaluation’s fast path. - R_
CONTINUATION_ 🔒TOKEN - Global continuation token for R_UnwindProtect.
Functions§
- call_
unwinding 🔒 ⚠ - Call
fun(data)in its ownR_UnwindProtect, in the caller’s R context, and return its value. - drain_
log_ 🔒queue_ if_ available - Drain the cross-thread log queue if the
logfeature is enabled. - eval_
unwinding 🔒 ⚠ - Evaluate
exprinenvwithRf_eval, in the caller’s R context: the condition handlers and restarts established around the.Call()stay in place, unlike underR_tryEval, whoseR_ToplevelExecempties the handler and restart stacks for the duration. An R exit leaves asRUnwind(call_unwinding). - get_
continuation_ 🔒token - Get or create the global continuation token.
- panic_
condition_ 🔒value - The tagged value of a generic panic,
kind = "panic": whatwith_r_unwind_protectreturns for one. - panic_
message_ 🔒with_ location - Stringify a panic payload and fold in the Rust source location the panic hook recorded on the current thread, producing the final R-facing message.
- panic_
payload_ to_ string - Extract a message from a panic payload.
- raise_
rust_ 🔒 ⚠condition_ via_ stop - Raise the R condition for a tagged condition value by evaluating the
shared
.miniextendr_raise_conditionhelper on it, the helper every generated R wrapper calls on the value its.Call()returns. - release_
abandoned_ 🔒 ⚠off_ main - Release the tokens of exits dropped off the main thread.
- resume_
if_ 🔒 ⚠r_ unwind - Resume
payloadif it carries an R exit (RUnwind) that belongs toboundary; return it otherwise, orRUnwind::strayfor an exit from elsewhere. Conditions deferred sincedeferred_markbelong to the call the exit abandons and are discarded first, as on the R-error path. - run_
r_ 🔒unwind_ protect - Core R_UnwindProtect wrapper. Returns
Ok(result)on success,Err(payload)on Rust panic, or diverges viaR_ContinueUnwindon R longjmp. - 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 the R error a generated wrapper would raise for it.
- 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.