Expand description
Worker thread infrastructure for safe Rust-R FFI.
§Why a worker thread at all?
R’s error handling uses longjmp, which skips Rust destructors and leaks
resources. To contain that, #[miniextendr]-generated function bodies run
on a separate Rust thread (the “worker”). R only longjmps on its own main
thread, so Rust frames on the worker are unwind-safe.
That split means user code is off the R main thread whenever the
worker-thread cargo feature is enabled. Anything that calls R’s C API
(allocating SEXPs, walking attributes, accessing INTEGER(x)) must
cross back to main via with_r_thread.
§Public API
with_r_thread— Execute a closure on R’s main thread. This is the bridge: call it from inside a#[miniextendr]body whenever you need to touch the R FFI.is_r_main_thread— Check if the current thread is R’s main thread.Sendable—#[doc(hidden)]wrapper used by the macros to ferrySEXP(and other!Sendtypes) across the worker channel. The author asserts the value is only consumed on the main thread.
§Tradeoffs
- Default to checked FFI variants (
Rf_allocVector,INTEGER, …) so the debug-assertion catches accidental off-thread calls. - Inside a
with_r_threadbody, the assertion is redundant — the*_uncheckedvariants incrate::sysare safe to call there (recognised by the lint MXL301, alongside ALTREP callbacks andcrate::unwind_protect::with_r_unwind_protectbodies). - Don’t raise R errors directly from worker-thread code.
Rf_errorwould longjmp through Rust frames on the wrong thread. Panic instead; the framework converts the panic into a structured R condition (seecrate::error_value). The lint MXL300 enforces this.
§Feature gate: worker-thread
Without the worker-thread cargo feature, all calls execute inline on
R’s main thread:
with_r_thread(f)runsf()directly (panics if not on main thread)run_on_worker(f)runsf()directly, returnsOk(f())
With the feature enabled, a dedicated worker thread is spawned at init time.
with_r_thread routes calls from the worker back to main, and run_on_worker
dispatches closures to the worker with bidirectional communication. The
worker has a 16 MB stack — keep proptest! invocations on it modest (see
the project CLAUDE.md).
§Initialization
miniextendr_runtime_init must be called from R’s main thread before any
R FFI APIs. Typically done in R_init_<pkgname>().
§Cross references
crate::unwind_protect::with_r_unwind_protect— catch R errors with Rust cleanup; sibling towith_r_thread.crate::sys— checked vs*_uncheckedFFI surface.crate::ffi_guard— guard taxonomy across boundaries.
Structs§
- Sendable 👻
- Wrapper to mark values as Send for main-thread routing.
Statics§
Functions§
- assert_
runtime_ 🔒initialized - Panic if the runtime hasn’t been initialized.
- has_
worker_ 🔒context - Check whether the current thread has a worker routing context.
- is_
r_ main_ thread - Check if the current thread is R’s main thread.
- miniextendr_
runtime_ 👻init - Initialize the miniextendr runtime.
- miniextendr_
runtime_ 👻shutdown - Shut down the miniextendr worker thread synchronously.
- panic_
message_ 👻to_ r_ error - Raise an R error from a panic message. Does not return.
- run_
on_ 👻worker - Run a closure on the worker thread with proper cleanup on panic.
- with_
r_ thread - Execute a closure on R’s main thread, returning the result.