Skip to main content

r_ffi_checked

Attribute Macro r_ffi_checked 

Source
#[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 through miniextendr_api::worker::with_r_thread when called from a worker.
  • A *_unchecked sibling (Rf_allocVector_unchecked) — the raw extern "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:

  1. 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.
  2. Inside a with_r_unwind_protect body — the guard has established main-thread context, and re-entering with_r_thread would nest two R_UnwindProtect frames (paying the longjmp-leak cost twice).
  3. Inside a with_r_thread body — 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

VariantAsserts main threadRoutes to mainWhen to use
Rf_foo (checked)yes (debug)yes (from worker)default
Rf_foo_uncheckednonoALTREP 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;
}