Reference page
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::ffi::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::ffi::{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::ffi::unif_rand;
fn generate_random() -> f64 {
let _guard = 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::ffi::unif_rand;
let values = with_rng(|| {
(0..10).map(|_| unsafe { unif_rand() }).collect::<Vec<_>>()
});π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::ffi:
| 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::ffi::{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)
]
}
}π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 crate
(rand, rand_chacha) with deterministic per-chunk seeding:
use rand::SeedableRng;
use rand_chacha::ChaChaRng;
use rand::Rng;
use miniextendr_api::rayon_bridge::with_r_vec;
#[miniextendr]
fn parallel_random(len: i32, seed: i64) -> SEXP {
with_r_vec(len as usize, |chunk: &mut [f64], offset| {
let mut rng = ChaChaRng::seed_from_u64(seed as u64 + offset as u64);
for slot in chunk.iter_mut() {
*slot = rng.gen();
}
})
}
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