RNG (Random Number Generation)
Safe access to R's random number generators from Rust.
Safe access to Rβs random number generators from Rust.
πOverview
Rβs RNG maintains internal state that must be synchronized with Rβs .Random.seed
variable. Before calling any RNG function, you must call GetRNGstate() to load
the state. After generating random numbers, you must call PutRNGstate() to save
it back β even if an error occurs. miniextendr provides two approaches: a proc-macro
attribute (recommended) and manual RAII guards.
πTable of Contents
- Quick Start
- The
#[miniextendr(rng)]Attribute - Manual Control: RngGuard and with_rng
- Available RNG Functions
- Combining with Other Attributes
- R Longjumps and Safety
- Parallel Code (Rayon)
πQuick Start
use miniextendr_api::sys::unif_rand;
#[miniextendr(rng)]
pub fn random_sample(n: i32) -> Vec<f64> {
(0..n).map(|_| unsafe { unif_rand() }).collect()
}
From R:
set.seed(42)
random_sample(5)
#> [1] 0.9148060 0.9370754 0.2861395 0.8304476 0.6417455πThe #[miniextendr(rng)] Attribute
The recommended approach. Works on standalone functions, impl methods, and trait methods.
πStandalone Functions
use miniextendr_api::sys::{unif_rand, norm_rand, exp_rand};
#[miniextendr(rng)]
pub fn uniform_sample(n: i32) -> Vec<f64> {
(0..n).map(|_| unsafe { unif_rand() }).collect()
}
#[miniextendr(rng)]
pub fn normal_sample(n: i32) -> Vec<f64> {
(0..n).map(|_| unsafe { norm_rand() }).collect()
}πImpl Methods
#[miniextendr]
impl MySampler {
#[miniextendr(rng)]
fn sample_uniform(&self, n: i32) -> Vec<f64> {
(0..n).map(|_| unsafe { unif_rand() }).collect()
}
#[miniextendr(rng)]
fn static_sample(n: i32) -> Vec<f64> {
(0..n).map(|_| unsafe { unif_rand() }).collect()
}
}πTrait Methods
#[miniextendr(env)]
impl MyTrait for MyStruct {
#[miniextendr(rng)]
fn random_value(&self) -> f64 {
unsafe { unif_rand() }
}
}πGenerated Code Pattern
The attribute generates code that:
- Calls
GetRNGstate()at the start - Wraps the function body in
catch_unwind - Calls
PutRNGstate()aftercatch_unwind(runs on both success AND panic) - Then handles the result (returns value or re-panics)
This explicit placement ensures PutRNGstate() is called before any error
handling, which is robust in the presence of R longjumps when combined with
with_r_unwind_protect.
πManual Control: RngGuard and with_rng
For internal helper functions or code that needs finer control over RNG scope.
πRngGuard
RAII guard that calls GetRNGstate() on creation and PutRNGstate() on drop.
use miniextendr_api::rng::RngGuard;
use miniextendr_api::sys::unif_rand;
fn generate_random() -> f64 {
// SAFETY: this helper is called on R's main thread.
let _guard = unsafe { RngGuard::new() };
unsafe { unif_rand() }
// PutRNGstate() called automatically when _guard drops
}πwith_rng
Convenience function that wraps a closure in an RngGuard scope.
use miniextendr_api::rng::with_rng;
use miniextendr_api::sys::unif_rand;
let values = with_rng(|| {
(0..10).map(|_| unsafe { unif_rand() }).collect::<Vec<_>>()
});
RngGuard::new() is unsafe because only the caller can prove it is running
on Rβs main thread. with_rng performs that check at runtime and panics when
called elsewhere. Both forms still rely on Rust destructors, so neither is a
replacement for #[miniextendr(rng)] at an R-facing boundary.
πWhen to Use Manual vs Attribute
| Scenario | Use |
|---|---|
| Function exposed to R | #[miniextendr(rng)] |
Internal helper (not #[miniextendr]) | RngGuard or with_rng |
Code already inside with_r_unwind_protect | RngGuard or with_rng |
| Scoped RNG within a larger function | RngGuard |
πAvailable RNG Functions
After initializing RNG state (via attribute or guard), use these from miniextendr_api::sys:
| Function | Distribution | Range |
|---|---|---|
unif_rand() | Uniform | [0, 1) |
norm_rand() | Standard normal | (-inf, inf) |
exp_rand() | Standard exponential | (0, inf) |
R_unif_index(n) | Uniform integer | [0, n) |
All require unsafe because they access Rβs global RNG state.
use miniextendr_api::sys::{unif_rand, norm_rand, exp_rand, R_unif_index};
#[miniextendr(rng)]
pub fn rng_demo() -> Vec<f64> {
unsafe {
vec![
unif_rand(), // Uniform on [0, 1)
norm_rand(), // Standard normal
exp_rand(), // Standard exponential
R_unif_index(100.0), // Uniform integer on [0, 100)
]
}
}πrand integration (feature = "rand")
Enable rand when downstream Rust code expects a rand RNG, or when you want
typed access to Rβs native distributions without calling the raw FFI yourself:
[dependencies]
miniextendr-api = { version = "0.1", features = ["rand"] }
RRng draws from Rβs RNG, so set.seed() remains reproducible. It still needs
an initialized RNG scope:
use miniextendr_api::{RDistributions, RRng};
use miniextendr_api::rand::RngExt;
#[miniextendr(rng)]
pub fn rand_sample(n: i32) -> Vec<f64> {
let mut rng = RRng::new();
(0..n)
.map(|_| rng.random::<f64>())
.chain([rng.standard_normal(), rng.standard_exp()])
.collect()
}
RRng supplies the generic rand interface; RDistributions calls Rβs
norm_rand, exp_rand, R_unif_index, and unif_rand implementations
directly. For large batches under worker-thread, generate the random values
together: each individual R RNG call must cross back to the R thread.
The same feature exposes RRngOps and RDistributionOps for exporting custom
RNG and distribution objects through the trait ABI. rand_distr additionally
re-exports the rand_distr crate for its probability distributions.
πCombining with Other Attributes
rng composes with other #[miniextendr] options:
// RNG + interrupt checking
#[miniextendr(rng, check_interrupt)]
pub fn interruptible_random(n: i32) -> Vec<f64> {
(0..n).map(|_| unsafe { unif_rand() }).collect()
}
// RNG + explicit worker thread
#[miniextendr(rng, worker)]
pub fn worker_random(n: i32) -> Vec<f64> {
(0..n).map(|_| unsafe { unif_rand() }).collect()
}πR Longjumps and Safety
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.
This is why #[miniextendr(rng)] is preferred for R-exposed functions β it
places PutRNGstate() explicitly after catch_unwind, outside the scope where
a longjmp could skip it.
Summary:
| Approach | Handles panics | Handles R longjumps |
|---|---|---|
#[miniextendr(rng)] | Yes | Yes |
RngGuard / with_rng | Yes (drop runs) | Only inside with_r_unwind_protect |
πParallel Code (Rayon)
Rβs RNG is not thread-safe. Calling unif_rand() and friends from Rayon
threads will panic. For parallel random number generation, use a Rust RNG with
deterministic per-chunk seeding:
use miniextendr_api::rand::{RngExt, SeedableRng, rngs::StdRng};
use miniextendr_api::rayon_bridge::with_r_vec;
#[miniextendr]
fn parallel_random(len: usize, seed: u64) -> SEXP {
with_r_vec(len, move |chunk: &mut [f64], offset| {
let offset = u64::try_from(offset).expect("R vector offset fits u64");
let mut rng = StdRng::seed_from_u64(seed.wrapping_add(offset));
for slot in chunk.iter_mut() {
*slot = rng.random();
}
})
}
See Rayon for full details on reproducible parallel RNG.
πSee Also
- Rayon Integration β Parallel computation (including RNG reproducibility)
#[miniextendr]Attribute β Complete attribute reference- Threads β Worker thread architecture