Skip to main content

Module rng

Module rng 

Source
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:

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.

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:

  1. Enters an outer with_r_unwind_protect guard and calls GetRNGstate()
  2. Runs the body under an inner guard, returning tagged conditions on Rust panics
  3. Protects that result while PutRNGstate() saves the state
  4. 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().

Use with_rng / RngGuard for:

  • 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 f with R’s RNG state loaded, handing it the guard to draw from.

Type Aliases§

NoSendSync 🔒
Enforces !Send + !Sync (R’s RNG state is only valid on the R main thread).