miniextendr_api/rng.rs
1//! RNG (Random Number Generation) utilities for R interop.
2//!
3//! This module provides safe wrappers around R's RNG state management functions.
4//! R's RNG must have its state loaded before generating random numbers, and
5//! the state must be saved back afterwards (even on error).
6//!
7//! # Background
8//!
9//! R's random number generators maintain internal state that must be synchronized
10//! with R's `.Random.seed` variable. Before calling any RNG function like
11//! [`unif_rand()`][crate::ffi::unif_rand], you must call `GetRNGstate()` to load
12//! the state. After generating random numbers, you must call `PutRNGstate()` to
13//! save it backāeven if an error occurs.
14//!
15//! # Available RNG Functions
16//!
17//! After initializing RNG state, you can use these functions from [`crate::ffi`]:
18//!
19//! - [`unif_rand()`][crate::ffi::unif_rand] - Uniform random on `[0, 1)`
20//! - [`norm_rand()`][crate::ffi::norm_rand] - Standard normal random
21//! - [`exp_rand()`][crate::ffi::exp_rand] - Standard exponential random
22//! - [`R_unif_index(n)`][crate::ffi::R_unif_index] - Uniform integer on `[0, n)`
23//!
24//! # Usage: The `#[miniextendr(rng)]` Attribute (Recommended)
25//!
26//! The simplest and safest way is to use the `#[miniextendr(rng)]` attribute on
27//! functions that need to generate random numbers:
28//!
29//! ```ignore
30//! use miniextendr_api::ffi::unif_rand;
31//!
32//! #[miniextendr(rng)]
33//! fn random_sample(n: i32) -> Vec<f64> {
34//! (0..n).map(|_| unsafe { unif_rand() }).collect()
35//! }
36//! ```
37//!
38//! This also works on impl methods and trait methods:
39//!
40//! ```ignore
41//! #[miniextendr]
42//! impl MyStruct {
43//! #[miniextendr(rng)]
44//! fn sample(&self, n: i32) -> Vec<f64> {
45//! (0..n).map(|_| unsafe { unif_rand() }).collect()
46//! }
47//! }
48//!
49//! #[miniextendr(env)]
50//! impl MyTrait for MyStruct {
51//! #[miniextendr(rng)]
52//! fn random_value(&self) -> f64 {
53//! unsafe { unif_rand() }
54//! }
55//! }
56//! ```
57//!
58//! ## Generated Code Pattern
59//!
60//! The `#[miniextendr(rng)]` attribute generates code that:
61//!
62//! 1. Calls `GetRNGstate()` at the start
63//! 2. Wraps the function body in `catch_unwind`
64//! 3. Calls `PutRNGstate()` after `catch_unwind` (runs on both success AND panic)
65//! 4. Then handles the result (returns value or re-panics)
66//!
67//! This explicit placement ensures `PutRNGstate()` is called before any error
68//! handling, which is robust in the presence of R longjumps when combined with
69//! `with_r_unwind_protect`.
70//!
71//! # Usage: Manual Control with [`RngGuard`]
72//!
73//! For code that isn't directly exposed to R, or when you need finer control,
74//! use [`RngGuard`]:
75//!
76//! ```ignore
77//! use miniextendr_api::rng::RngGuard;
78//! use miniextendr_api::ffi::unif_rand;
79//!
80//! fn generate_random() -> f64 {
81//! let _guard = RngGuard::new();
82//! unsafe { unif_rand() }
83//! // PutRNGstate() called automatically when _guard drops
84//! }
85//! ```
86//!
87//! Or use the [`with_rng`] convenience function:
88//!
89//! ```ignore
90//! use miniextendr_api::rng::with_rng;
91//! use miniextendr_api::ffi::unif_rand;
92//!
93//! let value = with_rng(|| unsafe { unif_rand() });
94//! ```
95//!
96//! # Important: R Longjumps
97//!
98//! [`RngGuard`] and [`with_rng`] rely on Rust's drop semantics. If R triggers a
99//! longjmp (via `Rf_error` etc.), the guard's destructor will NOT run unless
100//! the code is wrapped in `with_r_unwind_protect`.
101//!
102//! **For functions exposed to R, always prefer `#[miniextendr(rng)]`** which
103//! handles this correctly by using explicit placement of `PutRNGstate()`.
104//!
105//! Use [`RngGuard`] for:
106//! - Internal helper functions not directly exposed to R
107//! - Code already wrapped in `with_r_unwind_protect`
108//! - Scoped RNG access within a larger function
109
110use crate::ffi::{GetRNGstate, PutRNGstate};
111
112/// RAII guard for R's RNG state.
113///
114/// Calls `GetRNGstate()` on creation and `PutRNGstate()` on drop.
115/// This ensures RNG state is properly saved even if the function panics
116/// or returns early.
117///
118/// # Example
119///
120/// ```ignore
121/// use miniextendr_api::rng::RngGuard;
122/// use miniextendr_api::ffi::unif_rand;
123///
124/// fn generate_uniform() -> f64 {
125/// let _guard = RngGuard::new();
126/// unsafe { unif_rand() }
127/// }
128/// ```
129///
130/// # Warning: R Longjumps
131///
132/// This guard relies on Rust's drop semantics. If R triggers a longjmp
133/// (via `Rf_error` etc.), the destructor will NOT run unless the code
134/// is wrapped in `with_r_unwind_protect`. For functions exposed to R,
135/// prefer using `#[miniextendr(rng)]` which handles this correctly.
136///
137/// # Safety
138///
139/// Must be used on R's main thread. The guard assumes it has exclusive
140/// access to R's RNG state while alive.
141pub struct RngGuard {
142 _private: (), // Prevent construction outside this module
143}
144
145impl RngGuard {
146 /// Create a new RNG guard, loading the current RNG state.
147 ///
148 /// Calls `GetRNGstate()` to load R's `.Random.seed` into the RNG.
149 ///
150 /// # Safety
151 ///
152 /// Must be called from R's main thread.
153 #[inline]
154 pub fn new() -> Self {
155 unsafe { GetRNGstate() };
156 Self { _private: () }
157 }
158}
159
160impl Default for RngGuard {
161 fn default() -> Self {
162 Self::new()
163 }
164}
165
166impl Drop for RngGuard {
167 #[inline]
168 fn drop(&mut self) {
169 // Always save RNG state, even on panic
170 unsafe { PutRNGstate() };
171 }
172}
173
174/// Scope guard for RNG operations.
175///
176/// Executes a closure with RNG state properly managed.
177/// This is a convenience wrapper around [`RngGuard`].
178///
179/// # Example
180///
181/// ```ignore
182/// use miniextendr_api::rng::with_rng;
183/// use miniextendr_api::ffi::unif_rand;
184///
185/// let values = with_rng(|| {
186/// (0..10).map(|_| unsafe { unif_rand() }).collect::<Vec<_>>()
187/// });
188/// ```
189///
190/// # Warning
191///
192/// Like [`RngGuard`], this relies on Rust drop semantics and won't
193/// properly clean up if R longjumps. For R-exposed functions, use
194/// `#[miniextendr(rng)]` instead.
195#[inline]
196pub fn with_rng<F, R>(f: F) -> R
197where
198 F: FnOnce() -> R,
199{
200 let _guard = RngGuard::new();
201 f()
202}