miniextendr_api/rng.rs
1//! RNG (Random Number Generation) utilities for R interop.
2//!
3//! This module provides safe access to R's random number generators.
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 drawing, `GetRNGstate()` loads the
11//! state; after drawing, `PutRNGstate()` saves it back, even if an error occurs.
12//!
13//! An [`RngGuard`] is the proof that the state is loaded on R's main thread, so
14//! drawing is a safe method on it, named after R's C function:
15//!
16//! - [`RngGuard::unif_rand`] - Uniform random on `(0, 1)`
17//! - [`RngGuard::norm_rand`] - Standard normal random
18//! - [`RngGuard::exp_rand`] - Standard exponential random
19//! - [`RngGuard::unif_index`] - Uniform integer on `[0, n)` (`R_unif_index`)
20//!
21//! With the `rand` feature, `guard.rng()` gives an `RRng` that drives `rand`'s
22//! traits from R's generator for as long as it borrows the guard.
23//!
24//! # Usage: The `#[miniextendr(rng)]` Attribute (Recommended)
25//!
26//! The `rng` option loads and saves the state around the call. A parameter of
27//! type `&RngGuard` receives the guard the wrapper holds; it is no R argument
28//! (not in the R function's formals, usage line or `@param` list):
29//!
30//! ```ignore
31//! use miniextendr_api::RngGuard;
32//!
33//! #[miniextendr(rng)]
34//! fn random_sample(n: i32, rng: &RngGuard) -> Vec<f64> {
35//! (0..n).map(|_| rng.unif_rand()).collect()
36//! }
37//! ```
38//!
39//! From R the function is `random_sample(n)`. A `&RngGuard` parameter on a
40//! function without `rng` is a compile error: nothing has loaded the state.
41//!
42//! This also works on impl methods, in every class system:
43//!
44//! ```ignore
45//! #[miniextendr]
46//! impl MyStruct {
47//! #[miniextendr(rng)]
48//! fn sample(&self, n: i32, rng: &RngGuard) -> Vec<f64> {
49//! (0..n).map(|_| rng.norm_rand()).collect()
50//! }
51//! }
52//! ```
53//!
54//! A function or method that takes the guard always runs on R's main thread,
55//! whatever `worker` / `worker-default` says: the guard cannot leave it. Trait
56//! methods take no `&RngGuard` parameter; their arguments cross the trait ABI
57//! as R values.
58//!
59//! ## Generated Code Pattern
60//!
61//! The `#[miniextendr(rng)]` attribute generates code that:
62//!
63//! 1. Enters an outer `with_r_unwind_protect` guard and calls `GetRNGstate()`
64//! 2. Runs the body under an inner guard, returning tagged conditions on Rust panics
65//! 3. Protects that result while `PutRNGstate()` saves the state
66//! 4. Returns the value or tagged condition for the R wrapper to handle
67//!
68//! Both RNG calls can raise R errors, so both run inside the outer guard.
69//! Saving runs on successful returns and caught Rust panics. R-origin nonlocal
70//! exits propagate through the guards; they do not run the remaining body or
71//! save step. The guard handed to a `&RngGuard` parameter does not save the
72//! state when it drops: step 3 does.
73//!
74//! Inside an `rng` function, draw from the `&RngGuard` parameter rather than
75//! calling [`with_rng`]. A nested `GetRNGstate()` reloads `.Random.seed`, which
76//! still holds the state from before the call, so the draws would repeat.
77//!
78//! # Usage: Manual Control with [`with_rng`] and [`RngGuard`]
79//!
80//! For code that isn't directly exposed to R, or when you need finer control,
81//! use [`with_rng`]. It loads the state when it is called, so a `set.seed()`
82//! that ran in R before the call decides the draws:
83//!
84//! ```ignore
85//! use miniextendr_api::rng::with_rng;
86//!
87//! let value = with_rng(|rng| rng.unif_rand());
88//! ```
89//!
90//! Or create an [`RngGuard`] yourself:
91//!
92//! ```ignore
93//! use miniextendr_api::rng::RngGuard;
94//!
95//! fn generate_random() -> f64 {
96//! // SAFETY: called on R's main thread.
97//! let rng = unsafe { RngGuard::new() };
98//! rng.unif_rand()
99//! // PutRNGstate() called automatically when `rng` drops
100//! }
101//! ```
102//!
103//! # Important: R Longjumps
104//!
105//! [`RngGuard`] and [`with_rng`] rely on Rust's drop semantics. If R triggers a
106//! longjmp (via `Rf_error` etc.), the guard's destructor will NOT run unless
107//! the code is wrapped in `with_r_unwind_protect`.
108//!
109//! **For functions exposed to R, always prefer `#[miniextendr(rng)]`** which
110//! handles this correctly by using explicit placement of `PutRNGstate()`.
111//!
112//! Use [`with_rng`] / [`RngGuard`] for:
113//! - Internal helper functions not directly exposed to R
114//! - Code already wrapped in `with_r_unwind_protect`
115//! - Scoped RNG access within a larger function
116
117use crate::sys::{GetRNGstate, PutRNGstate};
118use std::marker::PhantomData;
119use std::rc::Rc;
120
121/// Enforces `!Send + !Sync` (R's RNG state is only valid on the R main thread).
122type NoSendSync = PhantomData<Rc<()>>;
123
124/// Proof that R's RNG state is loaded on R's main thread, and the way to draw
125/// from it.
126///
127/// A guard from [`RngGuard::new`] or [`with_rng`] calls `GetRNGstate()` when it
128/// is created and `PutRNGstate()` when it drops, so the state is saved even if
129/// the function panics or returns early. The draws ([`unif_rand`](Self::unif_rand),
130/// [`norm_rand`](Self::norm_rand), [`exp_rand`](Self::exp_rand),
131/// [`unif_index`](Self::unif_index)) borrow the guard, so none can run outside
132/// its scope. The guard is `!Send` and `!Sync`, so a draw can't run on another
133/// thread either.
134///
135/// # Example
136///
137/// ```ignore
138/// use miniextendr_api::rng::RngGuard;
139///
140/// fn generate_uniform() -> f64 {
141/// // SAFETY: called on R's main thread.
142/// let rng = unsafe { RngGuard::new() };
143/// rng.unif_rand()
144/// }
145/// ```
146///
147/// # Warning: R Longjumps
148///
149/// This guard relies on Rust's drop semantics. If R triggers a longjmp
150/// (via `Rf_error` etc.), the destructor will NOT run unless the code
151/// is wrapped in `with_r_unwind_protect`. For functions exposed to R,
152/// prefer using `#[miniextendr(rng)]` which handles this correctly.
153#[derive(Debug)]
154pub struct RngGuard {
155 /// Whether dropping the guard saves the state (`PutRNGstate()`). False
156 /// only for the guard a `#[miniextendr(rng)]` wrapper hands the body,
157 /// whose state the wrapper saves itself.
158 saves: bool,
159 // `!Send + !Sync` marker that also prevents construction outside this module.
160 _nosend: NoSendSync,
161}
162
163impl RngGuard {
164 /// Create a new RNG guard, loading the current RNG state.
165 ///
166 /// Calls `GetRNGstate()` to load R's `.Random.seed` into the RNG.
167 ///
168 /// # Safety
169 ///
170 /// Must be called from R's main thread — R's RNG state and the underlying
171 /// `GetRNGstate()` / `PutRNGstate()` calls are only valid there. Calling
172 /// this off the main thread is undefined behaviour. There is deliberately
173 /// no `Default` impl: constructing a guard is an unsafe, thread-scoped act,
174 /// not a safe default (#1096).
175 #[inline]
176 pub unsafe fn new() -> Self {
177 unsafe { GetRNGstate() };
178 Self {
179 saves: true,
180 _nosend: PhantomData,
181 }
182 }
183
184 /// The guard a `#[miniextendr(rng)]` wrapper passes to a `&RngGuard`
185 /// parameter. Generated code only.
186 ///
187 /// # Safety
188 ///
189 /// Must be called on R's main thread while the caller holds the state
190 /// loaded (`GetRNGstate()` has run) and saves it with `PutRNGstate()`
191 /// after the guard drops: dropping this guard saves nothing.
192 #[doc(hidden)]
193 #[inline]
194 pub unsafe fn __mx_loaded_by_wrapper() -> Self {
195 debug_assert!(
196 crate::worker::is_r_main_thread(),
197 "an `rng` wrapper's guard must be made on R's main thread"
198 );
199 Self {
200 saves: false,
201 _nosend: PhantomData,
202 }
203 }
204
205 /// A uniform draw on `(0, 1)`: R's `unif_rand()`, what `runif(1)` draws.
206 #[inline]
207 pub fn unif_rand(&self) -> f64 {
208 // SAFETY: a guard exists only on R's main thread with the state loaded.
209 unsafe { crate::sys::unif_rand_unchecked() }
210 }
211
212 /// A standard normal draw: R's `norm_rand()`, what `rnorm(1)` draws.
213 #[inline]
214 pub fn norm_rand(&self) -> f64 {
215 // SAFETY: a guard exists only on R's main thread with the state loaded.
216 unsafe { crate::sys::norm_rand_unchecked() }
217 }
218
219 /// A standard exponential draw (rate 1): R's `exp_rand()`, what `rexp(1)`
220 /// draws.
221 #[inline]
222 pub fn exp_rand(&self) -> f64 {
223 // SAFETY: a guard exists only on R's main thread with the state loaded.
224 unsafe { crate::sys::exp_rand_unchecked() }
225 }
226
227 /// A uniform integer on `[0, n)`, as a double: R's `R_unif_index(n)`, the
228 /// draw behind `sample()`. It uses the sample kind `RNGkind()` selects, so
229 /// `unif_index(n) + 1` draws what `sample.int(n, 1)` draws. Take `n > 0`.
230 #[inline]
231 pub fn unif_index(&self, n: f64) -> f64 {
232 // SAFETY: a guard exists only on R's main thread with the state loaded.
233 unsafe { crate::sys::R_unif_index_unchecked(n) }
234 }
235}
236
237impl Drop for RngGuard {
238 #[inline]
239 fn drop(&mut self) {
240 // Always save RNG state, even on panic, unless a wrapper saves it.
241 if self.saves {
242 unsafe { PutRNGstate() };
243 }
244 }
245}
246
247/// Run `f` with R's RNG state loaded, handing it the guard to draw from.
248///
249/// Loads the state (`GetRNGstate()`) when it is called, not before, so a
250/// `set.seed()` that ran in R before this call decides the draws. The state is
251/// saved (`PutRNGstate()`) when `f` returns or panics. This is a convenience
252/// wrapper around [`RngGuard`].
253///
254/// # Example
255///
256/// ```ignore
257/// use miniextendr_api::rng::with_rng;
258///
259/// let values = with_rng(|rng| (0..10).map(|_| rng.unif_rand()).collect::<Vec<_>>());
260/// ```
261///
262/// # Panics
263///
264/// Panics when called off R's main thread.
265///
266/// # Warning
267///
268/// Like [`RngGuard`], this relies on Rust drop semantics and won't
269/// properly clean up if R longjumps. For R-exposed functions, use
270/// `#[miniextendr(rng)]` instead, and draw from its `&RngGuard` parameter
271/// rather than calling `with_rng` inside it (see the module docs).
272#[inline]
273pub fn with_rng<F, R>(f: F) -> R
274where
275 F: FnOnce(&RngGuard) -> R,
276{
277 // SAFETY: `RngGuard::new()` requires the R main thread. Keep this check in
278 // release builds: otherwise this safe wrapper could invoke R's RNG API from
279 // a background thread and trigger undefined behaviour (#1096).
280 assert!(
281 crate::worker::is_r_main_thread(),
282 "with_rng must run on R's main thread"
283 );
284 let guard = unsafe { RngGuard::new() };
285 f(&guard)
286}
287
288#[cfg(test)]
289mod tests {
290 use super::RngGuard;
291
292 /// Compiles only for a type that is not `Send`: for a `Send` type both
293 /// impls apply and the call below is ambiguous.
294 trait AmbiguousIfSend<A> {
295 fn some_item() {}
296 }
297 impl<T: ?Sized> AmbiguousIfSend<()> for T {}
298 impl<T: ?Sized + Send> AmbiguousIfSend<u8> for T {}
299
300 /// Compiles only for a type that is not `Sync`.
301 trait AmbiguousIfSync<A> {
302 fn some_item() {}
303 }
304 impl<T: ?Sized> AmbiguousIfSync<()> for T {}
305 impl<T: ?Sized + Sync> AmbiguousIfSync<u8> for T {}
306
307 /// A guard stays on the thread that loaded the state, and so does a
308 /// borrow of it: a draw can't run on another thread.
309 #[test]
310 fn guard_is_neither_send_nor_sync() {
311 let _ = <RngGuard as AmbiguousIfSend<_>>::some_item;
312 let _ = <RngGuard as AmbiguousIfSync<_>>::some_item;
313 let _ = <&RngGuard as AmbiguousIfSend<_>>::some_item;
314 }
315}