Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 4 additions & 1 deletion src/rngs/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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};
Expand Down
87 changes: 76 additions & 11 deletions src/rngs/thread.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<ReseedingCore>,
}

/// 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<Self, SysError> {
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<u32, Infallible> {
Ok(self.inner.next_word())
}

#[inline(always)]
fn try_next_u64(&mut self) -> Result<u64, Infallible> {
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.
Expand Down Expand Up @@ -127,7 +183,7 @@ impl ReseedingCore {
#[derive(Clone)]
pub struct ThreadRng {
// Rc is explicitly !Send and !Sync
rng: Rc<UnsafeCell<BlockRng<ReseedingCore>>>,
rng: Rc<UnsafeCell<ThreadRngCore>>,
}

impl ThreadRng {
Expand All @@ -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()
}
}

Expand All @@ -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<UnsafeCell<BlockRng<ReseedingCore>>> = {
Rc::new(UnsafeCell::new(BlockRng::new(ReseedingCore {
inner: Core::try_from_rng(&mut SysRng).unwrap_or_else(|err| {
static THREAD_RNG_KEY: Rc<UnsafeCell<ThreadRngCore>> = {
Rc::new(UnsafeCell::new(ThreadRngCore::new().unwrap_or_else(|err| {
panic!("could not initialize ThreadRng: {}", err)
}),
})))
))
}
);

Expand Down Expand Up @@ -209,24 +263,23 @@ 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)]
fn try_next_u64(&mut self) -> Result<u64, Infallible> {
// 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)]
fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Infallible> {
// 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)
}
}

Expand All @@ -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::<i32>();
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 { .. }"
);
}
}