Skip to main content

pure_rng/
lib.rs

1//! PureRng is a [`rand`](https://crates.io/crates/rand)-compatible RNG library
2//! for generating repeatable, controlled random values, designed primarily for
3//! use in games. It uses a hash function to generate exactly one random value
4//! per seed. A convenient API is provided to distribute seed values throughout
5//! your program in a hierarchical fashion.
6//!
7//! # Usage
8//!
9//! ```rust
10//! use pure_rng::prelude::*;
11//!
12//! // Create the root rng using an initial seed, typically from an external source.
13//! let rng = PureRng::new(1234);
14//!
15//! // Seed two more RNGs with arbitrary labels.
16//! // These effectively have their seed appended to that of the parent.
17//! let rng_a = rng.seed("a convenient label");
18//! let rng_b = rng.seed("a different label");
19//!
20//! // Generate a value from the first RNG, consuming it.
21//! let value_a: u32 = rng_a.random();
22//!
23//! // Create two more forks of the second RNG, and generate values from them inline.
24//! let value_b1: i64 = rng_b.seed(1).random();
25//! let value_b2: f64 = rng_b.seed(2).random();
26//!
27//! // Use your custom types
28//! #[derive(Hash)]
29//! struct Point { x: i32, y: i32 }
30//!
31//! let value_from_point: u64 = rng
32//!     .seed(Point { x: 10, y: 12 })
33//!     .random();
34//!
35//! // Use the all the usual `rand` API features
36//! let character = rng
37//!     .seed("character")
38//!     .sample(rand::distr::Alphanumeric) as char;
39//! ```
40//!
41//! For more information see the README, the
42//! [accompanying blog post](https://jcd.pub/2025/03/14/pure-rng/), and the examples
43pub mod prelude;
44mod rng_core;
45pub mod seq;
46
47use rand::distr::uniform::SampleRange;
48use rand::distr::uniform::SampleUniform;
49use rand::distr::Distribution;
50use rand::distr::StandardUniform;
51use rand::Fill;
52use rand::Rng;
53use std::hash::{Hash, Hasher};
54
55/// Default generator using Rapidhash
56#[cfg(feature = "rapidhash")]
57pub type PureRng = PureRandomGenerator<rapidhash::RapidHasher>;
58
59/// Generic RNG type over any Hasher
60///
61/// Typically you would use [`PureRng`] for the default Rapidhash algorithm, or
62/// redefine that alias with your choice of hasher, rather than using this type
63/// directly.
64#[derive(Debug, Copy, Clone, Default, PartialEq, Eq)]
65#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
66pub struct PureRandomGenerator<H>
67where
68    H: Hasher + Default + Clone,
69{
70    hasher: H,
71}
72
73impl<H> PureRandomGenerator<H>
74where
75    H: Hasher + Default + Clone,
76{
77    /// Creates a new generator with the given hashable value as the seed. In a
78    /// game this might be the world seed, or just the system time etc.
79    ///
80    /// # Examples
81    ///
82    /// ```
83    /// use pure_rng::PureRng;
84    ///
85    /// let rng = PureRng::new("initial seed");
86    /// ```
87    pub fn new(hashable: impl Hash) -> Self {
88        Self::default().seed(hashable)
89    }
90
91    /// Forks the generator, and advances the fork's state by hashing the given
92    /// value.
93    ///
94    /// This is the core of the API - sometimes called "splitting" or "forking"
95    /// an RNG. The difference is that with PureRng you split every time you
96    /// generate a new value.
97    ///  
98    /// # Examples
99    ///
100    /// ```
101    /// use pure_rng::PureRng;
102    ///
103    /// let rng = PureRng::new("initial seed");
104    /// let sub_rng = rng.seed("a convenient label to differentiate");
105    /// let ten_values: Vec<u64> = (0..10).map(|i| sub_rng.seed(i).random()).collect();
106    ///
107    /// #[derive(Hash)]
108    /// struct Point { x: i32, y: i32 }
109    ///
110    /// let value_from_point: u64 = rng
111    ///     .seed(Point { x: 10, y: 12 })
112    ///     .random();
113    /// ```
114    ///
115    /// More usage example in the readme, and in `examples/complex.rs`
116    #[inline]
117    pub fn seed(&self, hashable: impl Hash) -> Self {
118        let mut fork = self.clone();
119        hashable.hash(&mut fork.hasher);
120
121        fork
122    }
123}
124/// Wrappers for the [`Rng`] trait functions.
125impl<H> PureRandomGenerator<H>
126where
127    H: Hasher + Default + Clone,
128{
129    /// Return a random value via the [`StandardUniform`] distribution.
130    ///
131    /// See [`Rng::random`].
132    ///
133    /// [`StandardUniform`]: rand::distr::StandardUniform
134    #[inline]
135    pub fn random<T>(mut self) -> T
136    where
137        StandardUniform: Distribution<T>,
138    {
139        Rng::random(&mut self)
140    }
141
142    /// Generate a random value in the given range.
143    ///
144    /// See [`Rng::random_range`].
145    #[track_caller]
146    pub fn random_range<T, Q>(mut self, range: Q) -> T
147    where
148        T: SampleUniform,
149        Q: SampleRange<T>,
150    {
151        Rng::random_range(&mut self, range)
152    }
153
154    /// Sample a new value, using the given distribution.
155    ///
156    /// See [`Rng::sample`].
157    pub fn sample<T, D: Distribution<T>>(mut self, distr: D) -> T {
158        Rng::sample(&mut self, distr)
159    }
160
161    /// Create an iterator that generates values using the given distribution.
162    ///
163    /// See [`Rng::sample_iter`].
164    pub fn sample_iter<T, D>(self, distr: D) -> rand::distr::Iter<D, Self, T>
165    where
166        D: Distribution<T>,
167        Self: Sized,
168    {
169        Rng::sample_iter(self, distr)
170    }
171
172    /// Fill any type implementing [`Fill`] with random data.
173    ///
174    /// See [`Rng::fill`].
175    #[track_caller]
176    pub fn fill<T: Fill + ?Sized>(mut self, dest: &mut T) {
177        Rng::fill(&mut self, dest)
178    }
179
180    /// Return a bool with a probability `p` of being true.
181    ///
182    /// See [`Rng::random_bool`].
183    #[inline]
184    #[track_caller]
185    pub fn random_bool(mut self, p: f64) -> bool {
186        Rng::random_bool(&mut self, p)
187    }
188
189    /// Return a bool with a probability of `numerator/denominator` of being
190    /// true.
191    ///
192    /// See [`Rng::random_ratio`].
193    #[inline]
194    #[track_caller]
195    pub fn random_ratio(mut self, numerator: u32, denominator: u32) -> bool {
196        Rng::random_ratio(&mut self, numerator, denominator)
197    }
198}
199
200#[cfg(test)]
201pub(crate) mod tests {
202    use crate::PureRng;
203
204    #[test]
205    fn test_repeatable() {
206        let rng = PureRng::default();
207
208        let val_1: u64 = rng.seed("lol").random();
209        let val_2: u64 = rng.seed("lol").random();
210        assert_eq!(val_1, val_2);
211
212        let rng_2 = rng.seed("foo");
213
214        let val_3: u64 = rng_2.seed("lol").random();
215        let val_4: u64 = rng_2.seed("lol").random();
216        assert_eq!(val_3, val_4);
217
218        assert_ne!(val_1, val_3);
219
220        let rng_3 = rng_2.seed("bar");
221
222        let val_5: u64 = rng_3.seed("lol").random();
223        let val_6: u64 = rng_3.seed("lol").random();
224        assert_eq!(val_5, val_6);
225
226        assert_ne!(val_3, val_5);
227        assert_ne!(val_2, val_5);
228    }
229}