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::sys::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::sys`]:
18//!
19//! - [`unif_rand()`][crate::sys::unif_rand] - Uniform random on `[0, 1)`
20//! - [`norm_rand()`][crate::sys::norm_rand] - Standard normal random
21//! - [`exp_rand()`][crate::sys::exp_rand] - Standard exponential random
22//! - [`R_unif_index(n)`][crate::sys::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::sys::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::sys::unif_rand;
79//!
80//! fn generate_random() -> f64 {
81//! // SAFETY: called on R's main thread.
82//! let _guard = unsafe { RngGuard::new() };
83//! unsafe { unif_rand() }
84//! // PutRNGstate() called automatically when _guard drops
85//! }
86//! ```
87//!
88//! Or use the [`with_rng`] convenience function:
89//!
90//! ```ignore
91//! use miniextendr_api::rng::with_rng;
92//! use miniextendr_api::sys::unif_rand;
93//!
94//! let value = with_rng(|| unsafe { unif_rand() });
95//! ```
96//!
97//! # Important: R Longjumps
98//!
99//! [`RngGuard`] and [`with_rng`] rely on Rust's drop semantics. If R triggers a
100//! longjmp (via `Rf_error` etc.), the guard's destructor will NOT run unless
101//! the code is wrapped in `with_r_unwind_protect`.
102//!
103//! **For functions exposed to R, always prefer `#[miniextendr(rng)]`** which
104//! handles this correctly by using explicit placement of `PutRNGstate()`.
105//!
106//! Use [`RngGuard`] for:
107//! - Internal helper functions not directly exposed to R
108//! - Code already wrapped in `with_r_unwind_protect`
109//! - Scoped RNG access within a larger function
110
111use crate::sys::{GetRNGstate, PutRNGstate};
112use std::marker::PhantomData;
113use std::rc::Rc;
114
115/// Enforces `!Send + !Sync` (R's RNG state is only valid on the R main thread).
116type NoSendSync = PhantomData<Rc<()>>;
117
118/// RAII guard for R's RNG state.
119///
120/// Calls `GetRNGstate()` on creation and `PutRNGstate()` on drop.
121/// This ensures RNG state is properly saved even if the function panics
122/// or returns early.
123///
124/// # Example
125///
126/// ```ignore
127/// use miniextendr_api::rng::RngGuard;
128/// use miniextendr_api::sys::unif_rand;
129///
130/// fn generate_uniform() -> f64 {
131/// // SAFETY: called on R's main thread.
132/// let _guard = unsafe { RngGuard::new() };
133/// unsafe { unif_rand() }
134/// }
135/// ```
136///
137/// # Warning: R Longjumps
138///
139/// This guard relies on Rust's drop semantics. If R triggers a longjmp
140/// (via `Rf_error` etc.), the destructor will NOT run unless the code
141/// is wrapped in `with_r_unwind_protect`. For functions exposed to R,
142/// prefer using `#[miniextendr(rng)]` which handles this correctly.
143///
144/// # Safety
145///
146/// Must be used on R's main thread. The guard assumes it has exclusive
147/// access to R's RNG state while alive.
148pub struct RngGuard {
149 // `!Send + !Sync` marker that also prevents construction outside this module.
150 _nosend: NoSendSync,
151}
152
153impl RngGuard {
154 /// Create a new RNG guard, loading the current RNG state.
155 ///
156 /// Calls `GetRNGstate()` to load R's `.Random.seed` into the RNG.
157 ///
158 /// # Safety
159 ///
160 /// Must be called from R's main thread — R's RNG state and the underlying
161 /// `GetRNGstate()` / `PutRNGstate()` calls are only valid there. Calling
162 /// this off the main thread is undefined behaviour. There is deliberately
163 /// no `Default` impl: constructing a guard is an unsafe, thread-scoped act,
164 /// not a safe default (#1096).
165 #[inline]
166 pub unsafe fn new() -> Self {
167 unsafe { GetRNGstate() };
168 Self {
169 _nosend: PhantomData,
170 }
171 }
172}
173
174impl Drop for RngGuard {
175 #[inline]
176 fn drop(&mut self) {
177 // Always save RNG state, even on panic
178 unsafe { PutRNGstate() };
179 }
180}
181
182/// Scope guard for RNG operations.
183///
184/// Executes a closure with RNG state properly managed.
185/// This is a convenience wrapper around [`RngGuard`].
186///
187/// # Example
188///
189/// ```ignore
190/// use miniextendr_api::rng::with_rng;
191/// use miniextendr_api::sys::unif_rand;
192///
193/// let values = with_rng(|| {
194/// (0..10).map(|_| unsafe { unif_rand() }).collect::<Vec<_>>()
195/// });
196/// ```
197///
198/// # Warning
199///
200/// Like [`RngGuard`], this relies on Rust drop semantics and won't
201/// properly clean up if R longjumps. For R-exposed functions, use
202/// `#[miniextendr(rng)]` instead.
203#[inline]
204pub fn with_rng<F, R>(f: F) -> R
205where
206 F: FnOnce() -> R,
207{
208 // SAFETY: `RngGuard::new()` requires the R main thread. Keep this check in
209 // release builds: otherwise this safe wrapper could invoke R's RNG API from
210 // a background thread and trigger undefined behaviour (#1096).
211 assert!(
212 crate::worker::is_r_main_thread(),
213 "with_rng must run on R's main thread"
214 );
215 let _guard = unsafe { RngGuard::new() };
216 f()
217}