From 458742dbc4ee97b0b092e735a7744475bf769270 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Tue, 16 Dec 2025 11:52:30 +0000 Subject: [PATCH 01/11] Move traits Word, Sealed into new word module; tweak docs --- src/le.rs | 48 +----------------------------------------------- src/lib.rs | 1 + src/word.rs | 51 +++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 53 insertions(+), 47 deletions(-) create mode 100644 src/word.rs diff --git a/src/le.rs b/src/le.rs index 9c1730d1..53231300 100644 --- a/src/le.rs +++ b/src/le.rs @@ -38,6 +38,7 @@ use crate::RngCore; #[allow(unused)] use crate::SeedableRng; +pub use crate::word::Word; /// Implement `next_u64` via `next_u32`, little-endian order. pub fn next_u64_via_u32(rng: &mut R) -> u64 { @@ -71,53 +72,6 @@ pub fn fill_bytes_via_next(rng: &mut R, dest: &mut [u8]) { } } -mod word { - pub trait Sealed: Copy { - type Bytes: Sized + AsRef<[u8]>; - - fn to_le_bytes(self) -> Self::Bytes; - - fn from_usize(val: usize) -> Self; - fn into_usize(self) -> usize; - } - - impl Sealed for u32 { - type Bytes = [u8; 4]; - - fn to_le_bytes(self) -> Self::Bytes { - Self::to_le_bytes(self) - } - - fn from_usize(val: usize) -> Self { - val.try_into().unwrap() - } - fn into_usize(self) -> usize { - self.try_into().unwrap() - } - } - - impl Sealed for u64 { - type Bytes = [u8; 8]; - - fn to_le_bytes(self) -> Self::Bytes { - Self::to_le_bytes(self) - } - - fn from_usize(val: usize) -> Self { - val.try_into().unwrap() - } - fn into_usize(self) -> usize { - self.try_into().unwrap() - } - } -} - -/// A marker trait for supported word types -/// -/// This is implemented for: `u32`, `u64`. -pub trait Word: word::Sealed {} -impl Word for W {} - /// Fill dest from src /// /// Returns `(n, byte_len)`. `src[..n]` is consumed, diff --git a/src/lib.rs b/src/lib.rs index c152835a..42afdb6b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -17,6 +17,7 @@ use core::{fmt, ops::DerefMut}; pub mod block; pub mod le; +mod word; /// Implementation-level interface for RNGs /// diff --git a/src/word.rs b/src/word.rs new file mode 100644 index 00000000..3fec730b --- /dev/null +++ b/src/word.rs @@ -0,0 +1,51 @@ +//! The [`Word`] trait + +/// A marker trait for supported "word" types. +/// +/// This is implemented for: `u32`, `u64`. +pub trait Word: sealed::Sealed {} + +impl Word for u32 {} +impl Word for u64 {} + +mod sealed { + /// Sealed trait implemented for `u32` and `u64`. + pub trait Sealed: Default + Copy + TryFrom + Eq + core::hash::Hash { + type Bytes: Sized + AsRef<[u8]>; + + fn to_le_bytes(self) -> Self::Bytes; + + fn from_usize(val: usize) -> Self; + fn into_usize(self) -> usize; + } + + impl Sealed for u32 { + type Bytes = [u8; 4]; + + fn to_le_bytes(self) -> Self::Bytes { + Self::to_le_bytes(self) + } + + fn from_usize(val: usize) -> Self { + val.try_into().unwrap() + } + fn into_usize(self) -> usize { + self.try_into().unwrap() + } + } + + impl Sealed for u64 { + type Bytes = [u8; 8]; + + fn to_le_bytes(self) -> Self::Bytes { + Self::to_le_bytes(self) + } + + fn from_usize(val: usize) -> Self { + val.try_into().unwrap() + } + fn into_usize(self) -> usize { + self.try_into().unwrap() + } + } +} From 5ba0f6a919bf02f83c74550ed547585a5d8e2593 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Tue, 16 Dec 2025 11:56:55 +0000 Subject: [PATCH 02/11] Replace fn fill_bytes_via_next with fill_bytes_via_next_word --- src/le.rs | 36 ++++++++++++++++-------------------- 1 file changed, 16 insertions(+), 20 deletions(-) diff --git a/src/le.rs b/src/le.rs index 53231300..599e8957 100644 --- a/src/le.rs +++ b/src/le.rs @@ -25,7 +25,7 @@ //! - [next_u64_via_fill][](self) //! //! **`fn fill_bytes`:** -//! - [fill_bytes_via_next][](self, dest) +//! - [fill_bytes_via_next_word][](self, dest) //! //! ### Implementing [`SeedableRng`] //! @@ -41,6 +41,7 @@ use crate::SeedableRng; pub use crate::word::Word; /// Implement `next_u64` via `next_u32`, little-endian order. +#[inline] pub fn next_u64_via_u32(rng: &mut R) -> u64 { // Use LE; we explicitly generate one value before the next. let x = u64::from(rng.next_u32()); @@ -48,27 +49,22 @@ pub fn next_u64_via_u32(rng: &mut R) -> u64 { (y << 32) | x } -/// Implement `fill_bytes` via `next_u64` and `next_u32`, little-endian order. +/// Fill `dst` with bytes using `next_word` /// -/// The fastest way to fill a slice is usually to work as long as possible with -/// integers. That is why this method mostly uses `next_u64`, and only when -/// there are 4 or less bytes remaining at the end of the slice it uses -/// `next_u32` once. -pub fn fill_bytes_via_next(rng: &mut R, dest: &mut [u8]) { - let mut left = dest; - while left.len() >= 8 { - let (l, r) = { left }.split_at_mut(8); - left = r; - let chunk: [u8; 8] = rng.next_u64().to_le_bytes(); - l.copy_from_slice(&chunk); +/// This may be used to implement [`RngCore::fill_bytes`] over `next_u32` or +/// `next_u64`. Words are used in order of generation. The last word may be +/// partially discarded. +#[inline] +pub fn fill_bytes_via_next_word(dst: &mut [u8], mut next_word: impl FnMut() -> W) { + let mut chunks = dst.chunks_exact_mut(size_of::()); + for chunk in &mut chunks { + let val = next_word(); + chunk.copy_from_slice(val.to_le_bytes().as_ref()); } - let n = left.len(); - if n > 4 { - let chunk: [u8; 8] = rng.next_u64().to_le_bytes(); - left.copy_from_slice(&chunk[..n]); - } else if n > 0 { - let chunk: [u8; 4] = rng.next_u32().to_le_bytes(); - left.copy_from_slice(&chunk[..n]); + let rem = chunks.into_remainder(); + if !rem.is_empty() { + let val = next_word().to_le_bytes(); + rem.copy_from_slice(&val.as_ref()[..rem.len()]); } } From 27004acb8e17d297c41a5a8b4acdda9f65dec8f0 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Tue, 16 Dec 2025 12:17:53 +0000 Subject: [PATCH 03/11] Add fn Sealed::from_le_bytes and #[inline(always)] annotations --- src/word.rs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/word.rs b/src/word.rs index 3fec730b..8c5b72f6 100644 --- a/src/word.rs +++ b/src/word.rs @@ -13,6 +13,7 @@ mod sealed { pub trait Sealed: Default + Copy + TryFrom + Eq + core::hash::Hash { type Bytes: Sized + AsRef<[u8]>; + fn from_le_bytes(bytes: Self::Bytes) -> Self; fn to_le_bytes(self) -> Self::Bytes; fn from_usize(val: usize) -> Self; @@ -22,13 +23,20 @@ mod sealed { impl Sealed for u32 { type Bytes = [u8; 4]; + #[inline(always)] + fn from_le_bytes(bytes: Self::Bytes) -> Self { + Self::from_le_bytes(bytes) + } + #[inline(always)] fn to_le_bytes(self) -> Self::Bytes { Self::to_le_bytes(self) } + #[inline(always)] fn from_usize(val: usize) -> Self { val.try_into().unwrap() } + #[inline(always)] fn into_usize(self) -> usize { self.try_into().unwrap() } @@ -37,13 +45,20 @@ mod sealed { impl Sealed for u64 { type Bytes = [u8; 8]; + #[inline(always)] + fn from_le_bytes(bytes: Self::Bytes) -> Self { + Self::from_le_bytes(bytes) + } + #[inline(always)] fn to_le_bytes(self) -> Self::Bytes { Self::to_le_bytes(self) } + #[inline(always)] fn from_usize(val: usize) -> Self { val.try_into().unwrap() } + #[inline(always)] fn into_usize(self) -> usize { self.try_into().unwrap() } From 22797a63d03a08cc09bd4044a3ffb79f1a5eb0b0 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Tue, 16 Dec 2025 12:25:56 +0000 Subject: [PATCH 04/11] Replace fns next_u{32,64}_via_fill with next_word_via_fill --- src/le.rs | 23 +++++++++-------------- src/word.rs | 2 +- 2 files changed, 10 insertions(+), 15 deletions(-) diff --git a/src/le.rs b/src/le.rs index 599e8957..147de91c 100644 --- a/src/le.rs +++ b/src/le.rs @@ -18,11 +18,11 @@ //! **`fn next_u32`:** //! - `self.next_u64() as u32` //! - `(self.next_u64() >> 32) as u32` -//! - [next_u32_via_fill][](self) +//! - [next_word_via_fill][](self) //! //! **`fn next_u64`:** //! - [next_u64_via_u32][](self) -//! - [next_u64_via_fill][](self) +//! - [next_word_via_fill][](self) //! //! **`fn fill_bytes`:** //! - [fill_bytes_via_next_word][](self, dest) @@ -98,18 +98,13 @@ pub(crate) fn fill_via_chunks(src: &[T], dest: &mut [u8]) -> (usize, us (num_chunks, byte_len) } -/// Implement `next_u32` via `fill_bytes`, little-endian order. -pub fn next_u32_via_fill(rng: &mut R) -> u32 { - let mut buf = [0; 4]; - rng.fill_bytes(&mut buf); - u32::from_le_bytes(buf) -} - -/// Implement `next_u64` via `fill_bytes`, little-endian order. -pub fn next_u64_via_fill(rng: &mut R) -> u64 { - let mut buf = [0; 8]; - rng.fill_bytes(&mut buf); - u64::from_le_bytes(buf) +/// Yield a word using [`RngCore::fill_bytes`] +/// +/// This may be used to implement `next_u32` or `next_u64`. +pub fn next_word_via_fill(rng: &mut R) -> W { + let mut buf: W::Bytes = Default::default(); + rng.fill_bytes(buf.as_mut()); + W::from_le_bytes(buf) } /// Fills `dst: &mut [u32]` from `src` diff --git a/src/word.rs b/src/word.rs index 8c5b72f6..de038c33 100644 --- a/src/word.rs +++ b/src/word.rs @@ -11,7 +11,7 @@ impl Word for u64 {} mod sealed { /// Sealed trait implemented for `u32` and `u64`. pub trait Sealed: Default + Copy + TryFrom + Eq + core::hash::Hash { - type Bytes: Sized + AsRef<[u8]>; + type Bytes: Default + Sized + AsRef<[u8]> + AsMut<[u8]>; fn from_le_bytes(bytes: Self::Bytes) -> Self; fn to_le_bytes(self) -> Self::Bytes; From 3c168c1ada7788b7d0a2cc49bb9127a0b7966521 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Tue, 16 Dec 2025 12:42:52 +0000 Subject: [PATCH 05/11] Replace fns read_u{32,64}_into with read_words --- src/block.rs | 6 +----- src/le.rs | 56 ++++++++++++++++++---------------------------------- src/lib.rs | 3 +-- 3 files changed, 21 insertions(+), 44 deletions(-) diff --git a/src/block.rs b/src/block.rs index 8b502603..b23a892a 100644 --- a/src/block.rs +++ b/src/block.rs @@ -39,11 +39,7 @@ //! fn from_seed(seed: Self::Seed) -> Self { //! let core = MyRngCore { //! // ... -//! # state: { -//! # let mut buf = [0u32; 8]; -//! # rand_core::le::read_u32_into(&seed, &mut buf); -//! # buf -//! # } +//! # state: rand_core::le::read_words(&seed), //! }; //! MyRng(BlockRng::new(core)) //! } diff --git a/src/le.rs b/src/le.rs index 147de91c..a7f66e22 100644 --- a/src/le.rs +++ b/src/le.rs @@ -29,11 +29,8 @@ //! //! ### Implementing [`SeedableRng`] //! -//! In many cases, [`SeedableRng::Seed`] must be converted to `[u32]` or -//! `[u64]`. The following helpers are provided: -//! -//! - [`read_u32_into`] -//! - [`read_u64_into`] +//! In many cases, [`SeedableRng::Seed`] must be converted to `[u32; _]` or +//! `[u64; _]`. [`read_words`] may be used for this. use crate::RngCore; #[allow(unused)] @@ -107,35 +104,24 @@ pub fn next_word_via_fill(rng: &mut R) -> W { W::from_le_bytes(buf) } -/// Fills `dst: &mut [u32]` from `src` +/// Reads an array of words from a byte slice /// -/// Reads use Little-Endian byte order, allowing portable reproduction of `dst` -/// from a byte slice. +/// Words are read from `src` in order, using LE conversion from bytes. /// /// # Panics /// -/// If `src` has insufficient length (if `src.len() < 4*dst.len()`). -#[inline] -#[track_caller] -pub fn read_u32_into(src: &[u8], dst: &mut [u32]) { - assert!(src.len() >= 4 * dst.len()); - for (out, chunk) in dst.iter_mut().zip(src.chunks_exact(4)) { - *out = u32::from_le_bytes(chunk.try_into().unwrap()); - } -} - -/// Fills `dst: &mut [u64]` from `src` -/// -/// # Panics -/// -/// If `src` has insufficient length (if `src.len() < 8*dst.len()`). -#[inline] -#[track_caller] -pub fn read_u64_into(src: &[u8], dst: &mut [u64]) { - assert!(src.len() >= 8 * dst.len()); - for (out, chunk) in dst.iter_mut().zip(src.chunks_exact(8)) { - *out = u64::from_le_bytes(chunk.try_into().unwrap()); +/// Panics if `size_of_val(src) != size_of::<[W; N]>()`. +#[inline(always)] +pub fn read_words(src: &[u8]) -> [W; N] { + assert_eq!(size_of_val(src), size_of::<[W; N]>()); + let mut dst = [W::from_usize(0); N]; + let chunks = src.chunks_exact(size_of::()); + for (out, chunk) in dst.iter_mut().zip(chunks) { + let mut buf: W::Bytes = Default::default(); + buf.as_mut().copy_from_slice(chunk); + *out = W::from_le_bytes(buf); } + dst } #[cfg(test)] @@ -186,23 +172,19 @@ mod test { fn test_read() { let bytes = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]; - let mut buf = [0u32; 4]; - read_u32_into(&bytes, &mut buf); + let buf: [u32; 4] = read_words(&bytes); assert_eq!(buf[0], 0x04030201); assert_eq!(buf[3], 0x100F0E0D); - let mut buf = [0u32; 3]; - read_u32_into(&bytes[1..13], &mut buf); // unaligned + let buf: [u32; 3] = read_words(&bytes[1..13]); // unaligned assert_eq!(buf[0], 0x05040302); assert_eq!(buf[2], 0x0D0C0B0A); - let mut buf = [0u64; 2]; - read_u64_into(&bytes, &mut buf); + let buf: [u64; 2] = read_words(&bytes); assert_eq!(buf[0], 0x0807060504030201); assert_eq!(buf[1], 0x100F0E0D0C0B0A09); - let mut buf = [0u64; 1]; - read_u64_into(&bytes[7..15], &mut buf); // unaligned + let buf: [u64; 1] = read_words(&bytes[7..15]); // unaligned assert_eq!(buf[0], 0x0F0E0D0C0B0A0908); } } diff --git a/src/lib.rs b/src/lib.rs index 42afdb6b..e1832b72 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -507,8 +507,7 @@ mod test { type Seed = [u8; 8]; fn from_seed(seed: Self::Seed) -> Self { - let mut x = [0u64; 1]; - le::read_u64_into(&seed, &mut x); + let x: [u64; 1] = le::read_words(&seed); SeedableNum(x[0]) } } From 8f9596790962489b147fb1f03ce3b6e12a09abf2 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Tue, 16 Dec 2025 12:55:41 +0000 Subject: [PATCH 06/11] Inline fill_via_chunks --- src/block.rs | 28 +++++++++++++++++---- src/le.rs | 70 ---------------------------------------------------- 2 files changed, 23 insertions(+), 75 deletions(-) diff --git a/src/block.rs b/src/block.rs index b23a892a..f1f3ad85 100644 --- a/src/block.rs +++ b/src/block.rs @@ -81,7 +81,7 @@ //! [`SeedableRng`]: crate::SeedableRng //! [`rand::rngs::ReseedingRng`]: https://docs.rs/rand/latest/rand/rngs/struct.ReseedingRng.html -use crate::le::{Word, fill_via_chunks}; +use crate::le::Word; use core::fmt; /// A random (block) generator @@ -292,11 +292,29 @@ impl> BlockRng { self.core.generate(&mut self.results); index = 0; } - let (consumed_u32, filled_u8) = - fill_via_chunks(&self.results[index..], &mut dest[read_len..]); - self.set_index(index + consumed_u32); - read_len += filled_u8; + let size = core::mem::size_of::(); + let mut chunks = dest[read_len..].chunks_exact_mut(size); + let mut src = self.results[index..].iter(); + + let zipped = chunks.by_ref().zip(src.by_ref()); + let num_chunks = zipped.len(); + zipped.for_each(|(chunk, src)| chunk.copy_from_slice(src.to_le_bytes().as_ref())); + index += num_chunks; + read_len += num_chunks * size; + + if let Some(src) = src.next() { + // We have consumed all full chunks of dest, but not src. + let dest = chunks.into_remainder(); + let n = dest.len(); + if n > 0 { + dest.copy_from_slice(&src.to_le_bytes().as_ref()[..n]); + index += 1; + read_len += n; + } + } + + self.set_index(index); } } } diff --git a/src/le.rs b/src/le.rs index a7f66e22..f2f94249 100644 --- a/src/le.rs +++ b/src/le.rs @@ -65,36 +65,6 @@ pub fn fill_bytes_via_next_word(dst: &mut [u8], mut next_word: impl FnM } } -/// Fill dest from src -/// -/// Returns `(n, byte_len)`. `src[..n]` is consumed, -/// `dest[..byte_len]` is filled. `src[n..]` and `dest[byte_len..]` are left -/// unaltered. -pub(crate) fn fill_via_chunks(src: &[T], dest: &mut [u8]) -> (usize, usize) { - let size = core::mem::size_of::(); - - // Always use little endian for portability of results. - - let mut dest = dest.chunks_exact_mut(size); - let mut src = src.iter(); - - let zipped = dest.by_ref().zip(src.by_ref()); - let num_chunks = zipped.len(); - zipped.for_each(|(dest, src)| dest.copy_from_slice(src.to_le_bytes().as_ref())); - - let byte_len = num_chunks * size; - if let Some(src) = src.next() { - // We have consumed all full chunks of dest, but not src. - let dest = dest.into_remainder(); - let n = dest.len(); - if n > 0 { - dest.copy_from_slice(&src.to_le_bytes().as_ref()[..n]); - return (num_chunks + 1, byte_len + n); - } - } - (num_chunks, byte_len) -} - /// Yield a word using [`RngCore::fill_bytes`] /// /// This may be used to implement `next_u32` or `next_u64`. @@ -128,46 +98,6 @@ pub fn read_words(src: &[u8]) -> [W; N] { mod test { use super::*; - #[test] - fn test_fill_via_u32_chunks() { - let src_orig = [1u32, 2, 3]; - - let src = src_orig; - let mut dst = [0u8; 11]; - assert_eq!(fill_via_chunks(&src, &mut dst), (3, 11)); - assert_eq!(dst, [1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0]); - - let src = src_orig; - let mut dst = [0u8; 13]; - assert_eq!(fill_via_chunks(&src, &mut dst), (3, 12)); - assert_eq!(dst, [1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0, 0]); - - let src = src_orig; - let mut dst = [0u8; 5]; - assert_eq!(fill_via_chunks(&src, &mut dst), (2, 5)); - assert_eq!(dst, [1, 0, 0, 0, 2]); - } - - #[test] - fn test_fill_via_u64_chunks() { - let src_orig = [1u64, 2]; - - let src = src_orig; - let mut dst = [0u8; 11]; - assert_eq!(fill_via_chunks(&src, &mut dst), (2, 11)); - assert_eq!(dst, [1, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0]); - - let src = src_orig; - let mut dst = [0u8; 17]; - assert_eq!(fill_via_chunks(&src, &mut dst), (2, 16)); - assert_eq!(dst, [1, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0]); - - let src = src_orig; - let mut dst = [0u8; 5]; - assert_eq!(fill_via_chunks(&src, &mut dst), (1, 5)); - assert_eq!(dst, [1, 0, 0, 0, 0]); - } - #[test] fn test_read() { let bytes = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]; From 6b63c44336eff60bc0acb23a1ae755fcd64deeb7 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Tue, 16 Dec 2025 13:31:59 +0000 Subject: [PATCH 07/11] Rename mod le -> utils --- src/block.rs | 4 ++-- src/lib.rs | 6 +++--- src/{le.rs => utils.rs} | 0 3 files changed, 5 insertions(+), 5 deletions(-) rename src/{le.rs => utils.rs} (100%) diff --git a/src/block.rs b/src/block.rs index f1f3ad85..82ac22d7 100644 --- a/src/block.rs +++ b/src/block.rs @@ -39,7 +39,7 @@ //! fn from_seed(seed: Self::Seed) -> Self { //! let core = MyRngCore { //! // ... -//! # state: rand_core::le::read_words(&seed), +//! # state: rand_core::utils::read_words(&seed), //! }; //! MyRng(BlockRng::new(core)) //! } @@ -81,7 +81,7 @@ //! [`SeedableRng`]: crate::SeedableRng //! [`rand::rngs::ReseedingRng`]: https://docs.rs/rand/latest/rand/rngs/struct.ReseedingRng.html -use crate::le::Word; +use crate::utils::Word; use core::fmt; /// A random (block) generator diff --git a/src/lib.rs b/src/lib.rs index e1832b72..a7323bc7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -16,7 +16,7 @@ use core::{fmt, ops::DerefMut}; pub mod block; -pub mod le; +pub mod utils; mod word; /// Implementation-level interface for RNGs @@ -51,7 +51,7 @@ mod word; /// /// Typically an RNG will implement only one of the methods available /// in this trait directly, then use the helper functions from the -/// [`le` module](crate::le) to implement the other methods. +/// [`utils`] module to implement the other methods. /// /// Note that implementors of [`RngCore`] also automatically implement /// the [`TryRngCore`] trait with the `Error` associated type being @@ -507,7 +507,7 @@ mod test { type Seed = [u8; 8]; fn from_seed(seed: Self::Seed) -> Self { - let x: [u64; 1] = le::read_words(&seed); + let x: [u64; 1] = utils::read_words(&seed); SeedableNum(x[0]) } } diff --git a/src/le.rs b/src/utils.rs similarity index 100% rename from src/le.rs rename to src/utils.rs From 18c9b9f2a26e31766f371a051a561f152a979eb2 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Tue, 16 Dec 2025 13:33:54 +0000 Subject: [PATCH 08/11] Utils: add doc example --- src/utils.rs | 60 +++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 55 insertions(+), 5 deletions(-) diff --git a/src/utils.rs b/src/utils.rs index f2f94249..34d97abc 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,15 +1,17 @@ -//! # Little-Endian utilities +//! Utilties to aid trait implementations +//! +//! ## Portability //! //! For cross-platform reproducibility, Little-Endian order (least-significant //! part first) has been chosen as the standard for inter-type conversion. -//! For example, ``next_u64_via_u32`] takes `u32` -//! values `x, y`, then outputs `(y << 32) | x`. +//! For example, [`next_u64_via_u32`] generates two `u32` values `x, y`, +//! then outputs `(y << 32) | x`. //! //! Byte-swapping (like the std `to_le` functions) is only needed to convert //! to/from byte sequences, and since its purpose is reproducibility, //! non-reproducible sources (e.g. `OsRng`) need not bother with it. //! -//! ### Implementing [`RngCore`] +//! ## Implementing [`RngCore`] //! //! Usually an implementation of [`RngCore`] will implement one of the three //! methods over its internal source. The following helpers are provided for @@ -27,10 +29,58 @@ //! **`fn fill_bytes`:** //! - [fill_bytes_via_next_word][](self, dest) //! -//! ### Implementing [`SeedableRng`] +//! ## Implementing [`SeedableRng`] //! //! In many cases, [`SeedableRng::Seed`] must be converted to `[u32; _]` or //! `[u64; _]`. [`read_words`] may be used for this. +//! +//! ## Example +//! +//! We demonstrate a simple "step RNG": +//! ``` +//! use rand_core::{RngCore, SeedableRng, utils}; +//! +//! pub struct Step32Rng { +//! state: u32 +//! } +//! +//! impl SeedableRng for Step32Rng { +//! type Seed = [u8; 4]; +//! +//! #[inline] +//! fn from_seed(seed: Self::Seed) -> Self { +//! // Always use little-endian byte order to ensure portable results +//! let state = u32::from_le_bytes(seed); +//! Self { state } +//! } +//! } +//! +//! impl RngCore for Step32Rng { +//! #[inline] +//! fn next_u32(&mut self) -> u32 { +//! let val = self.state; +//! self.state = val + 1; +//! val +//! } +//! +//! #[inline] +//! fn next_u64(&mut self) -> u64 { +//! utils::next_u64_via_u32(self) +//! } +//! +//! #[inline] +//! fn fill_bytes(&mut self, dst: &mut [u8]) { +//! utils::fill_bytes_via_next_word(dst, || self.next_u32()); +//! } +//! } +//! +//! # let mut rng = Step32Rng::seed_from_u64(42); +//! # assert_eq!(rng.next_u32(), 0x7ba1_8fa4); +//! # assert_eq!(rng.next_u64(), 0x7ba1_8fa6_7ba1_8fa5); +//! # let mut buf = [0u8; 5]; +//! # rng.fill_bytes(&mut buf); +//! # assert_eq!(buf, [0xa7, 0x8f, 0xa1, 0x7b, 0xa8]); +//! ``` use crate::RngCore; #[allow(unused)] From 50813a78b8683cc33522f191a82f4ff33a4a64a9 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Tue, 16 Dec 2025 13:48:46 +0000 Subject: [PATCH 09/11] Drop bounds on ::Output for impl Debug for BlockRng --- src/block.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/block.rs b/src/block.rs index 82ac22d7..db04f5a6 100644 --- a/src/block.rs +++ b/src/block.rs @@ -137,15 +137,14 @@ pub struct BlockRng { } // Custom Debug implementation that does not expose the contents of `results`. -impl fmt::Debug for BlockRng +impl fmt::Debug for BlockRng where - G: Generator + fmt::Debug, + G: Generator + fmt::Debug, { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { fmt.debug_struct("BlockRng") .field("core", &self.core) - .field("index", &self.index()) - .finish() + .finish_non_exhaustive() } } From 52994fabf051b65a698d448a16901e2f1dcf4cc8 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Wed, 17 Dec 2025 10:59:28 +0000 Subject: [PATCH 10/11] CHANGELOG --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ebf3a794..dc74d2a4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Remove impl of `RngCore` for `BlockRng`, making the latter more generic ([#34]) - Add trait `le::Word` ([#34]) - Add fn `BlockRng::reconstruct` and fn `BlockRng::remaining_results` ([#36]) +- Move `le::{Word, next_u64_via_u32}` to new `utils` module; remove `le` ([#38]) +- Replace `le::fill_bytes_via_next` with `utils::fill_bytes_via_next_word` ([#38]) +- Replace `le::next_u32_via_fill` and `le::next_u64_via_fill` with `utils::next_word_via_fill` ([#38]) +- Replace `le::read_u32_into` and `le::read_u64_into` with `utils::read_words` ([#38]) ### Other - Changed repository from [rust-random/rand] to [rust-random/core]. @@ -38,6 +42,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 [#34]: https://github.com/rust-random/rand-core/pull/34 [#35]: https://github.com/rust-random/rand-core/pull/35 [#36]: https://github.com/rust-random/rand-core/pull/36 +[#38]: https://github.com/rust-random/rand-core/pull/38 [rust-random/rand]: https://github.com/rust-random/rand [rust-random/core]: https://github.com/rust-random/core From d89a82f88fa5c5e5cd95551b3fc1a78d3f6fab17 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Wed, 17 Dec 2025 11:06:17 +0000 Subject: [PATCH 11/11] Use MCG in doc example --- src/utils.rs | 36 +++++++++++++++++------------------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/src/utils.rs b/src/utils.rs index 34d97abc..4fcd294c 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -36,50 +36,48 @@ //! //! ## Example //! -//! We demonstrate a simple "step RNG": +//! We demonstrate a simple multiplicative congruential generator (MCG), taken +//! from M.E. O'Neill's blog post +//! [Does It Beat the Minimal Standard?](https://www.pcg-random.org/posts/does-it-beat-the-minimal-standard.html). //! ``` //! use rand_core::{RngCore, SeedableRng, utils}; //! -//! pub struct Step32Rng { -//! state: u32 -//! } +//! pub struct Mcg128(u128); //! -//! impl SeedableRng for Step32Rng { -//! type Seed = [u8; 4]; +//! impl SeedableRng for Mcg128 { +//! type Seed = [u8; 16]; //! //! #[inline] //! fn from_seed(seed: Self::Seed) -> Self { //! // Always use little-endian byte order to ensure portable results -//! let state = u32::from_le_bytes(seed); -//! Self { state } +//! Self(u128::from_le_bytes(seed)) //! } //! } //! -//! impl RngCore for Step32Rng { +//! impl RngCore for Mcg128 { //! #[inline] //! fn next_u32(&mut self) -> u32 { -//! let val = self.state; -//! self.state = val + 1; -//! val +//! (self.next_u64() >> 32) as u32 //! } //! //! #[inline] //! fn next_u64(&mut self) -> u64 { -//! utils::next_u64_via_u32(self) +//! self.0 = self.0.wrapping_mul(0x0fc94e3bf4e9ab32866458cd56f5e605); +//! (self.0 >> 64) as u64 //! } //! //! #[inline] //! fn fill_bytes(&mut self, dst: &mut [u8]) { -//! utils::fill_bytes_via_next_word(dst, || self.next_u32()); +//! utils::fill_bytes_via_next_word(dst, || self.next_u64()); //! } //! } -//! -//! # let mut rng = Step32Rng::seed_from_u64(42); -//! # assert_eq!(rng.next_u32(), 0x7ba1_8fa4); -//! # assert_eq!(rng.next_u64(), 0x7ba1_8fa6_7ba1_8fa5); +//! # +//! # let mut rng = Mcg128::seed_from_u64(42); +//! # assert_eq!(rng.next_u32(), 3443086493); +//! # assert_eq!(rng.next_u64(), 3462997187007721903); //! # let mut buf = [0u8; 5]; //! # rng.fill_bytes(&mut buf); -//! # assert_eq!(buf, [0xa7, 0x8f, 0xa1, 0x7b, 0xa8]); +//! # assert_eq!(buf, [154, 23, 43, 68, 75]); //! ``` use crate::RngCore;