#[r_ffi_checked]Expand description
Generate thread-safe wrappers for R FFI functions.
Apply this to an extern "C-unwind" block to generate, for each
non-variadic function, a pair of entry points:
- The original name (e.g.
Rf_allocVector) — a safe Rust wrapper that debug-asserts the caller is on R’s main thread, routing throughminiextendr_api::worker::with_r_threadwhen called from a worker. - A
*_uncheckedsibling (Rf_allocVector_unchecked) — the rawextern "C-unwind"declaration with no main-thread assertion and no worker round-trip.
User code should reach for the checked variant by default; the unchecked sibling exists for three known-safe contexts:
- Inside ALTREP callbacks — R is already calling us on the main thread, so the assertion would always pass and the route would deadlock the call back to R.
- Inside a
with_r_unwind_protectbody — the guard has established main-thread context, and re-enteringwith_r_threadwould nest twoR_UnwindProtectframes (paying the longjmp-leak cost twice). - Inside a
with_r_threadbody — the assertion is redundant; you are already where you needed to be.
The build-time lint MXL301 enforces this: calling *_unchecked
outside one of those three contexts is a compile-time error. Outside
the worker-thread feature gate, the checked variant collapses to a thin
call and the two variants are observationally identical, but the lint
still applies so the same code is correct under --features worker-thread.
§Tradeoffs at a glance
| Variant | Asserts main thread | Routes to main | When to use |
|---|---|---|---|
Rf_foo (checked) | yes (debug) | yes (from worker) | default |
Rf_foo_unchecked | no | no | ALTREP callbacks, with_r_unwind_protect, with_r_thread |
§Behavior
All non-variadic functions are routed to the main thread via with_r_thread
when called from a worker thread. The return value is wrapped in Sendable
and sent back to the caller. This applies to both value-returning functions
(SEXP, i32, etc.) and pointer-returning functions (*const T, *mut T).
Pointer-returning functions (like INTEGER, REAL) are safe to route because
the underlying SEXP must be GC-protected by the caller, and R’s GC only runs
during R API calls which are serialized through with_r_thread.
§Initialization Requirement
miniextendr_runtime_init() must be called before using any wrapped function.
Calling before initialization will panic with a descriptive error message.
§Limitations
- Variadic functions are passed through unchanged (no wrapper)
- Statics are passed through unchanged
- Functions with
#[link_name]are passed through unchanged
§Example
#[r_ffi_checked]
unsafe extern "C-unwind" {
// Routed to main thread via with_r_thread when called from worker
pub fn Rf_ScalarInteger(arg1: i32) -> SEXP;
pub fn INTEGER(x: SEXP) -> *mut i32;
}