From 31aff822c6fdb21f599562a811e29ef6a0b405bc Mon Sep 17 00:00:00 2001 From: LiosK Date: Wed, 25 Feb 2026 17:26:51 -0500 Subject: [PATCH 1/2] Separate ThreadRng internal as ThreadRngCore This commit refactors `ThreadRng` to separate its internal PRNG and reseeding logic as `ThreadRngCore` so the new type could be exposed to public in the future for those who want to copy `ThreadRng` behavior with an independent state or in a Send + Sync context. See also #1748 --- src/rngs/thread.rs | 82 +++++++++++++++++++++++++++++++++++++++------- 1 file changed, 71 insertions(+), 11 deletions(-) diff --git a/src/rngs/thread.rs b/src/rngs/thread.rs index 63d334dae7..3d3f1d774b 100644 --- a/src/rngs/thread.rs +++ b/src/rngs/thread.rs @@ -74,6 +74,57 @@ impl ReseedingCore { } } +/// The [`ThreadRng`] internal +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`] + 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. + 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 +178,7 @@ impl ReseedingCore { #[derive(Clone)] pub struct ThreadRng { // Rc is explicitly !Send and !Sync - rng: Rc>>, + rng: Rc>, } impl ThreadRng { @@ -138,8 +189,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 +203,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 +258,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 +266,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 +274,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 +290,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 { .. }" + ); } } From b5851b194941ab97c02505b5ece1c0695c56f4b9 Mon Sep 17 00:00:00 2001 From: LiosK Date: Thu, 26 Feb 2026 06:35:58 -0500 Subject: [PATCH 2/2] Add `pub` and doc comments to `ThreadRngCore` to make it ready for re-export --- src/rngs/thread.rs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/rngs/thread.rs b/src/rngs/thread.rs index 3d3f1d774b..15c4be3a70 100644 --- a/src/rngs/thread.rs +++ b/src/rngs/thread.rs @@ -75,7 +75,11 @@ impl ReseedingCore { } /// The [`ThreadRng`] internal -struct ThreadRngCore { +/// +/// This type is the actual pseudo-random 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, } @@ -88,7 +92,7 @@ impl fmt::Debug for ThreadRngCore { impl ThreadRngCore { /// Initialize the generator using [`SysRng`] - fn new() -> Result { + pub fn new() -> Result { Core::try_from_rng(&mut SysRng).map(|result| Self { inner: BlockRng::new(ReseedingCore { inner: result }), }) @@ -97,7 +101,7 @@ impl ThreadRngCore { /// Immediately reseed the generator /// /// This discards any remaining random data in the cache. - fn reseed(&mut self) -> Result<(), SysError> { + pub fn reseed(&mut self) -> Result<(), SysError> { self.inner.reset_and_skip(0); self.inner.core.reseed() } @@ -140,7 +144,7 @@ impl TryCryptoRng for ThreadRngCore {} /// /// - Automatic seeding via [`SysRng`] and after every 64 kB of output. /// Limitation: there is no automatic reseeding on process fork (see [below](#fork)). -/// - A rigorusly analyzed, unpredictable (cryptographic) pseudo-random generator +/// - A rigorously analyzed, unpredictable (cryptographic) pseudo-random generator /// (see [the book on security](https://rust-random.github.io/book/guide-rngs.html#security)). /// The currently selected algorithm is ChaCha (12-rounds). /// See also [`StdRng`] documentation.