diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c32e4ef14..490110a47d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,13 @@ A [separate changelog is kept for rand_core](https://github.com/rust-random/core You may also find the [Upgrade Guide](https://rust-random.github.io/book/update.html) useful. +## [Unreleased] + +### Additions +- Expose `ThreadRngCore` ([#1750]) + +[#1750]: https://github.com/rust-random/rand/pull/1750 + ## [0.10.0] - 2026-02-08 ### Changes diff --git a/src/rngs/mod.rs b/src/rngs/mod.rs index dabc3ab97e..37e622c0b1 100644 --- a/src/rngs/mod.rs +++ b/src/rngs/mod.rs @@ -52,6 +52,9 @@ //! //! ### Additional generators //! +//! - [`ThreadRngCore`] is the underlying, periodically seeded PRNG of +//! [`ThreadRng`] that allows users to utilize the same reseeding generator +//! where `Send` or `Sync` is required. //! - The [`rdrand`] crate provides an interface to the RDRAND and RDSEED //! instructions available in modern Intel and AMD CPUs. //! - The [`rand_jitter`] crate provides a user-space implementation of @@ -110,7 +113,7 @@ pub use xoshiro256plusplus::Xoshiro256PlusPlus; #[cfg(feature = "std_rng")] pub use self::std::StdRng; #[cfg(feature = "thread_rng")] -pub use self::thread::ThreadRng; +pub use self::thread::{ThreadRng, ThreadRngCore}; #[cfg(feature = "chacha")] pub use chacha20::{ChaCha8Rng, ChaCha12Rng, ChaCha20Rng}; diff --git a/src/rngs/thread.rs b/src/rngs/thread.rs index 9dd535c5ba..804d330cfc 100644 --- a/src/rngs/thread.rs +++ b/src/rngs/thread.rs @@ -74,6 +74,62 @@ impl ReseedingCore { } } +/// The [`ThreadRng`] internal +/// +/// This type is the underlying, periodically seeded pseudorandom number generator that powers +/// [`ThreadRng`]. The same Security design criteria and consideration as those of [`ThreadRng`] +/// apply, whereas it allows users to utilize the same reseeding generator where `Send` or `Sync` +/// is required. +pub struct ThreadRngCore { + inner: BlockRng, +} + +/// Debug implementation does not leak internal state +impl fmt::Debug for ThreadRngCore { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + write!(fmt, "ThreadRngCore {{ .. }}") + } +} + +impl ThreadRngCore { + /// Initialize the generator using [`SysRng`] + pub fn new() -> Result { + Core::try_from_rng(&mut SysRng).map(|result| Self { + inner: BlockRng::new(ReseedingCore { inner: result }), + }) + } + + /// Immediately reseed the generator + /// + /// This discards any remaining random data in the cache. + pub fn reseed(&mut self) -> Result<(), SysError> { + self.inner.reset_and_skip(0); + self.inner.core.reseed() + } +} + +impl TryRng for ThreadRngCore { + type Error = Infallible; + + #[inline(always)] + fn try_next_u32(&mut self) -> Result { + Ok(self.inner.next_word()) + } + + #[inline(always)] + fn try_next_u64(&mut self) -> Result { + Ok(self.inner.next_u64_from_u32()) + } + + #[inline(always)] + fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Infallible> { + self.inner.fill_bytes(dest); + Ok(()) + } +} + +impl TryCryptoRng for ThreadRngCore {} + /// A reference to the thread-local generator /// /// This type is a reference to a lazily-initialized thread-local generator. @@ -127,7 +183,7 @@ impl ReseedingCore { #[derive(Clone)] pub struct ThreadRng { // Rc is explicitly !Send and !Sync - rng: Rc>>, + rng: Rc>, } impl ThreadRng { @@ -138,8 +194,7 @@ impl ThreadRng { // SAFETY: We must make sure to stop using `rng` before anyone else // creates another mutable reference let rng = unsafe { &mut *self.rng.get() }; - rng.reset_and_skip(0); - rng.core.reseed() + rng.reseed() } } @@ -153,12 +208,11 @@ impl fmt::Debug for ThreadRng { thread_local!( // We require Rc<..> to avoid premature freeing when ThreadRng is used // within thread-local destructors. See #968. - static THREAD_RNG_KEY: Rc>> = { - Rc::new(UnsafeCell::new(BlockRng::new(ReseedingCore { - inner: Core::try_from_rng(&mut SysRng).unwrap_or_else(|err| { + static THREAD_RNG_KEY: Rc> = { + Rc::new(UnsafeCell::new(ThreadRngCore::new().unwrap_or_else(|err| { panic!("could not initialize ThreadRng: {}", err) }), - }))) + )) } ); @@ -209,7 +263,7 @@ impl TryRng for ThreadRng { // SAFETY: We must make sure to stop using `rng` before anyone else // creates another mutable reference let rng = unsafe { &mut *self.rng.get() }; - Ok(rng.next_word()) + rng.try_next_u32() } #[inline(always)] @@ -217,7 +271,7 @@ impl TryRng for ThreadRng { // SAFETY: We must make sure to stop using `rng` before anyone else // creates another mutable reference let rng = unsafe { &mut *self.rng.get() }; - Ok(rng.next_u64_from_u32()) + rng.try_next_u64() } #[inline(always)] @@ -225,8 +279,7 @@ impl TryRng for ThreadRng { // SAFETY: We must make sure to stop using `rng` before anyone else // creates another mutable reference let rng = unsafe { &mut *self.rng.get() }; - rng.fill_bytes(dest); - Ok(()) + rng.try_fill_bytes(dest) } } @@ -242,10 +295,22 @@ mod test { assert_eq!(r.random_range(0..1), 0); } + #[test] + fn test_thread_rng_core() { + use crate::RngExt; + let mut r = super::ThreadRngCore::new().unwrap(); + r.random::(); + assert_eq!(r.random_range(0..1), 0); + } + #[test] fn test_debug_output() { // We don't care about the exact output here, but it must not include // private CSPRNG state or the cache stored by BlockRng! assert_eq!(std::format!("{:?}", crate::rng()), "ThreadRng { .. }"); + assert_eq!( + std::format!("{:?}", super::ThreadRngCore::new().unwrap()), + "ThreadRngCore { .. }" + ); } }