Expand description
RNG (Random Number Generation) utilities for R interop.
This module provides safe access to R’s random number generators. R’s RNG must have its state loaded before generating random numbers, and the state must be saved back afterwards (even on error).
§Background
R’s random number generators maintain internal state that must be synchronized
with R’s .Random.seed variable. Before drawing, GetRNGstate() loads the
state; after drawing, PutRNGstate() saves it back, even if an error occurs.
An RngGuard is the proof that the state is loaded on R’s main thread, so
drawing is a safe method on it, named after R’s C function:
RngGuard::unif_rand- Uniform random on(0, 1)RngGuard::norm_rand- Standard normal randomRngGuard::exp_rand- Standard exponential randomRngGuard::unif_index- Uniform integer on[0, n)(R_unif_index)
With the rand feature, guard.rng() gives an RRng that drives rand’s
traits from R’s generator for as long as it borrows the guard.
§Usage: The #[miniextendr(rng)] Attribute (Recommended)
The rng option loads and saves the state around the call. A parameter of
type &RngGuard receives the guard the wrapper holds; it is no R argument
(not in the R function’s formals, usage line or @param list):
use miniextendr_api::RngGuard;
#[miniextendr(rng)]
fn random_sample(n: i32, rng: &RngGuard) -> Vec<f64> {
(0..n).map(|_| rng.unif_rand()).collect()
}From R the function is random_sample(n). A &RngGuard parameter on a
function without rng is a compile error: nothing has loaded the state.
This also works on impl methods, in every class system:
#[miniextendr]
impl MyStruct {
#[miniextendr(rng)]
fn sample(&self, n: i32, rng: &RngGuard) -> Vec<f64> {
(0..n).map(|_| rng.norm_rand()).collect()
}
}A function or method that takes the guard always runs on R’s main thread,
whatever worker / worker-default says: the guard cannot leave it. Trait
methods take no &RngGuard parameter; their arguments cross the trait ABI
as R values.
§Generated Code Pattern
The #[miniextendr(rng)] attribute generates code that:
- Enters an outer
with_r_unwind_protectguard and callsGetRNGstate() - Runs the body under an inner guard, returning tagged conditions on Rust panics
- Protects that result while
PutRNGstate()saves the state - Returns the value or tagged condition for the R wrapper to handle
Both RNG calls can raise R errors, so both run inside the outer guard.
Saving runs on successful returns and caught Rust panics. R-origin nonlocal
exits propagate through the guards; they do not run the remaining body or
save step. The guard handed to a &RngGuard parameter does not save the
state when it drops: step 3 does.
Inside an rng function, draw from the &RngGuard parameter rather than
calling with_rng. A nested GetRNGstate() reloads .Random.seed, which
still holds the state from before the call, so the draws would repeat.
§Usage: Manual Control with with_rng and RngGuard
For code that isn’t directly exposed to R, or when you need finer control,
use with_rng. It loads the state when it is called, so a set.seed()
that ran in R before the call decides the draws:
use miniextendr_api::rng::with_rng;
let value = with_rng(|rng| rng.unif_rand());Or create an RngGuard yourself:
use miniextendr_api::rng::RngGuard;
fn generate_random() -> f64 {
// SAFETY: called on R's main thread.
let rng = unsafe { RngGuard::new() };
rng.unif_rand()
// PutRNGstate() called automatically when `rng` drops
}§Important: R Longjumps
RngGuard and with_rng rely on Rust’s drop semantics. If R triggers a
longjmp (via Rf_error etc.), the guard’s destructor will NOT run unless
the code is wrapped in with_r_unwind_protect.
For functions exposed to R, always prefer #[miniextendr(rng)] which
handles this correctly by using explicit placement of PutRNGstate().
- Internal helper functions not directly exposed to R
- Code already wrapped in
with_r_unwind_protect - Scoped RNG access within a larger function
Structs§
- RngGuard
- Proof that R’s RNG state is loaded on R’s main thread, and the way to draw from it.
Functions§
- with_
rng - Run
fwith R’s RNG state loaded, handing it the guard to draw from.
Type Aliases§
- NoSend
Sync 🔒 - Enforces
!Send + !Sync(R’s RNG state is only valid on the R main thread).