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

Structs§

Boundary 🔒
A miniextendr boundary that resumes R exits (RUnwind): the innermost on its thread from enter until it is dropped or leaves.
PreLocatedPanic 🔒
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_unwinding carries 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 Boundary on this thread; 0 outside 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_ReleaseObject would race R. call_unwinding releases them on the main thread.
HAS_ABANDONED_OFF_MAIN 🔒
Whether ABANDONED_OFF_MAIN may 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 own R_UnwindProtect, in the caller’s R context, and return its value.
drain_log_queue_if_available 🔒
Drain the cross-thread log queue if the log feature is enabled.
eval_unwinding 🔒 ⚠
Evaluate expr in env with Rf_eval, in the caller’s R context: the condition handlers and restarts established around the .Call() stay in place, unlike under R_tryEval, whose R_ToplevelExec empties the handler and restart stacks for the duration. An R exit leaves as RUnwind (call_unwinding).
get_continuation_token 🔒
Get or create the global continuation token.
panic_condition_value 🔒
The tagged value of a generic panic, kind = "panic": what with_r_unwind_protect returns 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_condition helper 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 payload if it carries an R exit (RUnwind) that belongs to boundary; return it otherwise, or RUnwind::stray for an exit from elsewhere. Conditions deferred since deferred_mark belong 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 via R_ContinueUnwind on 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 custom PanicSource.