artichoke_backend/extn/core/random/
mod.rs

1//! Random provides an interface to Ruby's pseudo-random number generator, or
2//! PRNG. The PRNG produces a deterministic sequence of bits which approximate
3//! true randomness. The sequence may be represented by integers, floats, or
4//! binary strings.
5//!
6//! This module implements the [`Random`] singleton object from Ruby Core.
7//!
8//! In Artichoke, `Random` is implemented using a modified Mersenne Twister that
9//! reproduces the same byte and float sequences as the MRI implementation.
10//!
11//! You can use this class in your application by accessing it directly. As a
12//! Core API, it is globally available:
13//!
14//! ```ruby
15//! Random::DEFAULT.bytes(16)
16//! r = Random.new(33)
17//! r.rand
18//! ```
19//!
20//! [`Random`]: https://ruby-doc.org/core-3.1.2/Random.html
21
22use spinoso_random::{InitializeError, NewSeedError, UrandomError};
23#[doc(inline)]
24pub use spinoso_random::{Max, Rand, Random};
25
26use crate::convert::{HeapAllocatedData, implicitly_convert_to_int};
27use crate::extn::prelude::*;
28
29pub(in crate::extn) mod mruby;
30pub(super) mod trampoline;
31
32#[derive(Debug, Clone, Hash, PartialEq, Eq)]
33enum Rng {
34    Global,
35    Instance(Box<Random>),
36}
37
38impl HeapAllocatedData for Rng {
39    const RUBY_TYPE: &'static str = "Random";
40}
41
42#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
43pub enum Seed {
44    New(i64),
45    None,
46}
47
48impl Default for Seed {
49    fn default() -> Self {
50        Self::new()
51    }
52}
53
54impl From<i64> for Seed {
55    fn from(seed: i64) -> Seed {
56        Seed::New(seed)
57    }
58}
59
60impl Seed {
61    /// Construct a an empty seed.
62    #[must_use]
63    pub const fn new() -> Self {
64        Self::None
65    }
66
67    #[must_use]
68    pub fn from_mt_seed_lossy(seed: [u32; 4]) -> Self {
69        // TODO: return a bignum instead of truncating.
70        let seed = {
71            let [hi, lo, _, _] = seed;
72            ((i64::from(hi)) << 32) | i64::from(lo)
73        };
74
75        Self::New(seed)
76    }
77
78    #[must_use]
79    pub fn to_mt_seed(self) -> Option<[u32; 4]> {
80        if let Self::New(seed) = self {
81            let seed = i128::from(seed);
82            let seed = seed.to_le_bytes();
83            let seed = spinoso_random::seed_to_key(seed);
84            Some(seed)
85        } else {
86            None
87        }
88    }
89}
90
91impl TryConvert<Seed, Value> for Artichoke {
92    type Error = Error;
93
94    fn try_convert(&self, seed: Seed) -> Result<Value, Self::Error> {
95        match seed {
96            Seed::None => Ok(Value::nil()),
97            Seed::New(seed) => self.try_convert(seed),
98        }
99    }
100}
101
102impl TryConvertMut<Value, Seed> for Artichoke {
103    type Error = Error;
104
105    fn try_convert_mut(&mut self, value: Value) -> Result<Seed, Self::Error> {
106        let seed = implicitly_convert_to_int(self, value)?;
107        Ok(Seed::New(seed))
108    }
109}
110
111impl TryConvertMut<Option<Value>, Seed> for Artichoke {
112    type Error = Error;
113
114    fn try_convert_mut(&mut self, value: Option<Value>) -> Result<Seed, Self::Error> {
115        if let Some(value) = value {
116            let seed = self.try_convert_mut(value)?;
117            Ok(seed)
118        } else {
119            Ok(Seed::None)
120        }
121    }
122}
123
124impl TryConvertMut<Value, Max> for Artichoke {
125    type Error = Error;
126
127    fn try_convert_mut(&mut self, max: Value) -> Result<Max, Self::Error> {
128        let optional: Option<Value> = self.try_convert(max)?;
129        self.try_convert_mut(optional)
130    }
131}
132
133impl TryConvertMut<Option<Value>, Max> for Artichoke {
134    type Error = Error;
135
136    fn try_convert_mut(&mut self, max: Option<Value>) -> Result<Max, Self::Error> {
137        if let Some(max) = max {
138            match max.ruby_type() {
139                Ruby::Fixnum => {
140                    let max = max.try_convert_into(self)?;
141                    Ok(Max::Integer(max))
142                }
143                Ruby::Float => {
144                    let max = max.try_convert_into(self)?;
145                    Ok(Max::Float(max))
146                }
147                _ => {
148                    let max = implicitly_convert_to_int(self, max).map_err(|_| {
149                        let mut message = b"invalid argument - ".to_vec();
150                        message.extend(max.inspect(self));
151                        ArgumentError::from(message)
152                    })?;
153                    Ok(Max::Integer(max))
154                }
155            }
156        } else {
157            Ok(Max::None)
158        }
159    }
160}
161
162impl ConvertMut<Rand, Value> for Artichoke {
163    fn convert_mut(&mut self, from: Rand) -> Value {
164        match from {
165            Rand::Integer(num) => self.convert(num),
166            Rand::Float(num) => self.convert_mut(num),
167        }
168    }
169}
170
171impl From<spinoso_random::ArgumentError> for Error {
172    fn from(err: spinoso_random::ArgumentError) -> Self {
173        // XXX: Should this be an `ArgumentError`?
174        let err = RuntimeError::from(err.to_string());
175        err.into()
176    }
177}
178
179impl From<InitializeError> for Error {
180    fn from(err: InitializeError) -> Self {
181        let err = RuntimeError::from(err.message());
182        err.into()
183    }
184}
185
186impl From<NewSeedError> for Error {
187    fn from(err: NewSeedError) -> Self {
188        let err = RuntimeError::from(err.message());
189        err.into()
190    }
191}
192
193impl From<UrandomError> for Error {
194    fn from(err: UrandomError) -> Self {
195        let err = RuntimeError::from(err.message());
196        err.into()
197    }
198}
199
200#[cfg(test)]
201mod tests {
202    use super::Seed;
203
204    #[test]
205    fn test_seed_new() {
206        let seed = Seed::new();
207        assert_eq!(seed, Seed::None);
208    }
209
210    #[test]
211    fn test_from_mt_seed_lossy_basic() {
212        let input = [0x1234_5678, 0x9ABC_DEF0, 0x0, 0x0];
213        let seed = Seed::from_mt_seed_lossy(input);
214        // High 32 bits: `0x1234_5678`, Low 32 bits: `0x9ABC_DEF0`
215        assert_eq!(seed, Seed::New((0x123_45678_i64 << 32) | 0x9ABC_DEF0));
216    }
217
218    #[test]
219    fn test_from_mt_seed_lossy_max_values() {
220        let input = [u32::MAX, u32::MAX, 0x0, 0x0];
221        let seed = Seed::from_mt_seed_lossy(input);
222        // High 32 bits: `u32::MAX`, Low 32 bits: `u32::MAX`
223        assert_eq!(seed, Seed::New((i64::from(u32::MAX) << 32) | i64::from(u32::MAX)));
224    }
225
226    #[test]
227    fn test_from_mt_seed_lossy_discarded_values() {
228        let input = [0x1234_5678, 0x9AB_CDEF0, 0xDEAD_BEEF, 0xFEED_FACE];
229        let seed = Seed::from_mt_seed_lossy(input);
230        // High 32 bits: `0x12345678`, Low 32 bits: `0x9ABCDEF0`
231        // The other values are discarded
232        assert_eq!(seed, Seed::New((0x1234_5678_i64 << 32) | 0x9ABC_DEF0));
233    }
234
235    #[test]
236    fn test_from_mt_seed_lossy_zero_values() {
237        let input = [0x0, 0x0, 0x0, 0x0];
238        let seed = Seed::from_mt_seed_lossy(input);
239        // All values are zero
240        assert_eq!(seed, Seed::New(0));
241    }
242
243    #[test]
244    fn test_from_mt_seed_lossy_high_only() {
245        let input = [0x1234_5678, 0x0, 0x0, 0x0];
246        let seed = Seed::from_mt_seed_lossy(input);
247        // High 32 bits: `0x1234_5678`, Low 32 bits: `0x0`
248        assert_eq!(seed, Seed::New(0x1234_5678 << 32));
249    }
250
251    #[test]
252    fn test_from_mt_seed_lossy_low_only() {
253        let input = [0x0, 0x9ABC_DEF0, 0x0, 0x0];
254        let seed = Seed::from_mt_seed_lossy(input);
255        // High 32 bits: `0x0`, Low 32 bits: `0x9ABC_DEF0`
256        assert_eq!(seed, Seed::New(0x9ABC_DEF0));
257    }
258}