From b902a60b5f362e5caaef79400f6ab515fe05d70c Mon Sep 17 00:00:00 2001 From: Vinzent Steinberg Date: Sun, 26 Apr 2026 21:30:55 +0200 Subject: [PATCH 1/4] rand_xoshiro: Add `state()` for serde-free state export Add a `state()` method to all 15 RNG types in `rand_xoshiro`, returning the internal state as a value matching the type's `SeedableRng::Seed`. This lets callers persist and reload generator state without enabling the `serde` feature: `Self::from_seed(rng.state())` reconstructs an identical generator. Return type per RNG matches its `Seed`: | RNG | `state()` | | --------------------------------------------------------- | ----------- | | `SplitMix64`, `Xoroshiro64Star`, `Xoroshiro64StarStar` | `[u8; 8]` | | `Xoroshiro128*`, `Xoshiro128*` | `[u8; 16]` | | `Xoshiro256*` | `[u8; 32]` | | `Xoshiro512*` | `Seed512` | The all-zero state is unreachable from any non-zero seed for these algorithms, so the round-trip is exact for any generator obtained via the usual constructors. (`from_seed` remaps an all-zero input to `seed_from_u64(0)`; this is documented on each `state()`.) Implementation lives in four small macros in `common.rs` (`impl_state_scalar!`, `impl_state_pair!`, `impl_state_array!`, `impl_state_seed512!`); each RNG file gets one macro invocation and one `state_roundtrip` test. Fixes #109, #64. --- rand_xoshiro/CHANGELOG.md | 7 ++ rand_xoshiro/src/common.rs | 93 ++++++++++++++++++++++++ rand_xoshiro/src/splitmix64.rs | 14 ++++ rand_xoshiro/src/xoroshiro128plus.rs | 14 ++++ rand_xoshiro/src/xoroshiro128plusplus.rs | 14 ++++ rand_xoshiro/src/xoroshiro128starstar.rs | 14 ++++ rand_xoshiro/src/xoroshiro64star.rs | 14 ++++ rand_xoshiro/src/xoroshiro64starstar.rs | 14 ++++ rand_xoshiro/src/xoshiro128plus.rs | 14 ++++ rand_xoshiro/src/xoshiro128plusplus.rs | 14 ++++ rand_xoshiro/src/xoshiro128starstar.rs | 14 ++++ rand_xoshiro/src/xoshiro256plus.rs | 14 ++++ rand_xoshiro/src/xoshiro256plusplus.rs | 14 ++++ rand_xoshiro/src/xoshiro256starstar.rs | 14 ++++ rand_xoshiro/src/xoshiro512plus.rs | 14 ++++ rand_xoshiro/src/xoshiro512plusplus.rs | 14 ++++ rand_xoshiro/src/xoshiro512starstar.rs | 14 ++++ 17 files changed, 310 insertions(+) diff --git a/rand_xoshiro/CHANGELOG.md b/rand_xoshiro/CHANGELOG.md index e537b409..f4bfb72a 100644 --- a/rand_xoshiro/CHANGELOG.md +++ b/rand_xoshiro/CHANGELOG.md @@ -5,10 +5,17 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +### Added +- `state()` method on every RNG type returning the internal state as a value + matching `SeedableRng::Seed`, allowing reproduction via `from_seed` without + the `serde` feature ([#110]). + ### Changed - Shrink the code size of the all-zero-seed fallback by replacing the per-RNG `SplitMix64::seed_from_u64(0)` call with a single shared constant. +[#110]: https://github.com/rust-random/rngs/pull/110 + ## [0.8.0] - 2026-02-01 ### Changes - Use Edition 2024 and MSRV 1.85 ([#73]) diff --git a/rand_xoshiro/src/common.rs b/rand_xoshiro/src/common.rs index f7be5636..c29d96f9 100644 --- a/rand_xoshiro/src/common.rs +++ b/rand_xoshiro/src/common.rs @@ -222,6 +222,99 @@ macro_rules! impl_xoshiro_large { }; } +/// Implement `state`, returning the internal state of a single-word RNG as +/// little-endian bytes that round-trip through [`SeedableRng::from_seed`]. +macro_rules! impl_state_scalar { + ($type:ident) => { + impl $type { + /// Return the internal state of the generator as little-endian + /// bytes that can be passed to [`SeedableRng::from_seed`] to + /// reconstruct an identical generator. + /// + /// [`SeedableRng::from_seed`]: rand_core::SeedableRng::from_seed + pub fn state(&self) -> [u8; 8] { + self.x.to_le_bytes() + } + } + }; +} + +/// Implement `state` for an RNG with two word-sized fields `s0` and `s1`. +macro_rules! impl_state_pair { + ($type:ident, $word:ty, $bytes:literal) => { + impl $type { + /// Return the internal state of the generator as little-endian + /// bytes that can be passed to [`SeedableRng::from_seed`] to + /// reconstruct an identical generator. + /// + /// The all-zero state is unreachable from any non-zero seed, so + /// the round-trip is exact for any generator obtained via the + /// usual constructors. (An all-zero input to `from_seed` is + /// remapped to `seed_from_u64(0)`.) + /// + /// [`SeedableRng::from_seed`]: rand_core::SeedableRng::from_seed + pub fn state(&self) -> [u8; $bytes] { + let mut out = [0u8; $bytes]; + let n = core::mem::size_of::<$word>(); + out[..n].copy_from_slice(&self.s0.to_le_bytes()); + out[n..].copy_from_slice(&self.s1.to_le_bytes()); + out + } + } + }; +} + +/// Implement `state` for an RNG with a `s: [WORD; N]` field. +macro_rules! impl_state_array { + ($type:ident, $word:ty, $bytes:literal) => { + impl $type { + /// Return the internal state of the generator as little-endian + /// bytes that can be passed to [`SeedableRng::from_seed`] to + /// reconstruct an identical generator. + /// + /// The all-zero state is unreachable from any non-zero seed, so + /// the round-trip is exact for any generator obtained via the + /// usual constructors. (An all-zero input to `from_seed` is + /// remapped to `seed_from_u64(0)`.) + /// + /// [`SeedableRng::from_seed`]: rand_core::SeedableRng::from_seed + pub fn state(&self) -> [u8; $bytes] { + let mut out = [0u8; $bytes]; + let n = core::mem::size_of::<$word>(); + for (i, word) in self.s.iter().enumerate() { + out[i * n..(i + 1) * n].copy_from_slice(&word.to_le_bytes()); + } + out + } + } + }; +} + +/// Implement `state` for a 512-bit RNG (`s: [u64; 8]`), returning a [`Seed512`]. +macro_rules! impl_state_seed512 { + ($type:ident) => { + impl $type { + /// Return the internal state of the generator as a [`Seed512`] + /// that can be passed to [`SeedableRng::from_seed`] to reconstruct + /// an identical generator. + /// + /// The all-zero state is unreachable from any non-zero seed, so + /// the round-trip is exact for any generator obtained via the + /// usual constructors. (An all-zero input to `from_seed` is + /// remapped to `seed_from_u64(0)`.) + /// + /// [`SeedableRng::from_seed`]: rand_core::SeedableRng::from_seed + pub fn state(&self) -> crate::Seed512 { + let mut out = [0u8; 64]; + for (i, word) in self.s.iter().enumerate() { + out[i * 8..(i + 1) * 8].copy_from_slice(&word.to_le_bytes()); + } + crate::Seed512(out) + } + } + }; +} + /// Fallback seed used when `from_seed` is called with an all-zero input. /// /// Equal to the first 64 bytes of `SplitMix64::seed_from_u64(0)`'s output. diff --git a/rand_xoshiro/src/splitmix64.rs b/rand_xoshiro/src/splitmix64.rs index da408430..98dac717 100644 --- a/rand_xoshiro/src/splitmix64.rs +++ b/rand_xoshiro/src/splitmix64.rs @@ -60,6 +60,8 @@ impl TryRng for SplitMix64 { } } +impl_state_scalar!(SplitMix64); + impl SeedableRng for SplitMix64 { type Seed = [u8; 8]; @@ -142,6 +144,18 @@ mod tests { } } + #[test] + fn state_roundtrip() { + let mut rng = SplitMix64::seed_from_u64(42); + for _ in 0..10 { + rng.next_u64(); + } + let mut clone = SplitMix64::from_seed(rng.state()); + for _ in 0..10 { + assert_eq!(rng.next_u64(), clone.next_u64()); + } + } + #[test] fn next_u32() { let mut rng = SplitMix64::seed_from_u64(10); diff --git a/rand_xoshiro/src/xoroshiro128plus.rs b/rand_xoshiro/src/xoroshiro128plus.rs index 2e844d73..f44e2aab 100644 --- a/rand_xoshiro/src/xoroshiro128plus.rs +++ b/rand_xoshiro/src/xoroshiro128plus.rs @@ -80,6 +80,8 @@ impl TryRng for Xoroshiro128Plus { utils::fill_bytes_via_next_word(dest, || self.try_next_u64()) } } +impl_state_pair!(Xoroshiro128Plus, u64, 16); + impl SeedableRng for Xoroshiro128Plus { type Seed = [u8; 16]; @@ -129,4 +131,16 @@ mod tests { let from_sm0 = Xoroshiro128Plus::seed_from_u64(0); assert_eq!(from_zero, from_sm0); } + + #[test] + fn state_roundtrip() { + let mut rng = Xoroshiro128Plus::seed_from_u64(42); + for _ in 0..10 { + rng.next_u64(); + } + let mut clone = Xoroshiro128Plus::from_seed(rng.state()); + for _ in 0..10 { + assert_eq!(rng.next_u64(), clone.next_u64()); + } + } } diff --git a/rand_xoshiro/src/xoroshiro128plusplus.rs b/rand_xoshiro/src/xoroshiro128plusplus.rs index 1496ede0..a929b2f7 100644 --- a/rand_xoshiro/src/xoroshiro128plusplus.rs +++ b/rand_xoshiro/src/xoroshiro128plusplus.rs @@ -78,6 +78,8 @@ impl TryRng for Xoroshiro128PlusPlus { } } +impl_state_pair!(Xoroshiro128PlusPlus, u64, 16); + impl SeedableRng for Xoroshiro128PlusPlus { type Seed = [u8; 16]; @@ -128,4 +130,16 @@ mod tests { let from_sm0 = Xoroshiro128PlusPlus::seed_from_u64(0); assert_eq!(from_zero, from_sm0); } + + #[test] + fn state_roundtrip() { + let mut rng = Xoroshiro128PlusPlus::seed_from_u64(42); + for _ in 0..10 { + rng.next_u64(); + } + let mut clone = Xoroshiro128PlusPlus::from_seed(rng.state()); + for _ in 0..10 { + assert_eq!(rng.next_u64(), clone.next_u64()); + } + } } diff --git a/rand_xoshiro/src/xoroshiro128starstar.rs b/rand_xoshiro/src/xoroshiro128starstar.rs index 199276e6..f46c6712 100644 --- a/rand_xoshiro/src/xoroshiro128starstar.rs +++ b/rand_xoshiro/src/xoroshiro128starstar.rs @@ -78,6 +78,8 @@ impl TryRng for Xoroshiro128StarStar { } } +impl_state_pair!(Xoroshiro128StarStar, u64, 16); + impl SeedableRng for Xoroshiro128StarStar { type Seed = [u8; 16]; @@ -128,4 +130,16 @@ mod tests { let from_sm0 = Xoroshiro128StarStar::seed_from_u64(0); assert_eq!(from_zero, from_sm0); } + + #[test] + fn state_roundtrip() { + let mut rng = Xoroshiro128StarStar::seed_from_u64(42); + for _ in 0..10 { + rng.next_u64(); + } + let mut clone = Xoroshiro128StarStar::from_seed(rng.state()); + for _ in 0..10 { + assert_eq!(rng.next_u64(), clone.next_u64()); + } + } } diff --git a/rand_xoshiro/src/xoroshiro64star.rs b/rand_xoshiro/src/xoroshiro64star.rs index a3807576..d9bf732e 100644 --- a/rand_xoshiro/src/xoroshiro64star.rs +++ b/rand_xoshiro/src/xoroshiro64star.rs @@ -49,6 +49,8 @@ impl TryRng for Xoroshiro64Star { } } +impl_state_pair!(Xoroshiro64Star, u32, 8); + impl SeedableRng for Xoroshiro64Star { type Seed = [u8; 8]; @@ -97,4 +99,16 @@ mod tests { let from_sm0 = Xoroshiro64Star::seed_from_u64(0); assert_eq!(from_zero, from_sm0); } + + #[test] + fn state_roundtrip() { + let mut rng = Xoroshiro64Star::seed_from_u64(42); + for _ in 0..10 { + rng.next_u32(); + } + let mut clone = Xoroshiro64Star::from_seed(rng.state()); + for _ in 0..10 { + assert_eq!(rng.next_u32(), clone.next_u32()); + } + } } diff --git a/rand_xoshiro/src/xoroshiro64starstar.rs b/rand_xoshiro/src/xoroshiro64starstar.rs index 6592b14c..e3e1b920 100644 --- a/rand_xoshiro/src/xoroshiro64starstar.rs +++ b/rand_xoshiro/src/xoroshiro64starstar.rs @@ -48,6 +48,8 @@ impl TryRng for Xoroshiro64StarStar { } } +impl_state_pair!(Xoroshiro64StarStar, u32, 8); + impl SeedableRng for Xoroshiro64StarStar { type Seed = [u8; 8]; @@ -96,4 +98,16 @@ mod tests { let from_sm0 = Xoroshiro64StarStar::seed_from_u64(0); assert_eq!(from_zero, from_sm0); } + + #[test] + fn state_roundtrip() { + let mut rng = Xoroshiro64StarStar::seed_from_u64(42); + for _ in 0..10 { + rng.next_u32(); + } + let mut clone = Xoroshiro64StarStar::from_seed(rng.state()); + for _ in 0..10 { + assert_eq!(rng.next_u32(), clone.next_u32()); + } + } } diff --git a/rand_xoshiro/src/xoshiro128plus.rs b/rand_xoshiro/src/xoshiro128plus.rs index 7c03008a..60b63229 100644 --- a/rand_xoshiro/src/xoshiro128plus.rs +++ b/rand_xoshiro/src/xoshiro128plus.rs @@ -56,6 +56,8 @@ impl Xoshiro128Plus { } } +impl_state_array!(Xoshiro128Plus, u32, 16); + impl SeedableRng for Xoshiro128Plus { type Seed = [u8; 16]; @@ -143,4 +145,16 @@ mod tests { let from_sm0 = Xoshiro128Plus::seed_from_u64(0); assert_eq!(from_zero, from_sm0); } + + #[test] + fn state_roundtrip() { + let mut rng = Xoshiro128Plus::seed_from_u64(42); + for _ in 0..10 { + rng.next_u32(); + } + let mut clone = Xoshiro128Plus::from_seed(rng.state()); + for _ in 0..10 { + assert_eq!(rng.next_u32(), clone.next_u32()); + } + } } diff --git a/rand_xoshiro/src/xoshiro128plusplus.rs b/rand_xoshiro/src/xoshiro128plusplus.rs index bcb95983..d7d2f525 100644 --- a/rand_xoshiro/src/xoshiro128plusplus.rs +++ b/rand_xoshiro/src/xoshiro128plusplus.rs @@ -55,6 +55,8 @@ impl Xoshiro128PlusPlus { } } +impl_state_array!(Xoshiro128PlusPlus, u32, 16); + impl SeedableRng for Xoshiro128PlusPlus { type Seed = [u8; 16]; @@ -145,4 +147,16 @@ mod tests { let from_sm0 = Xoshiro128PlusPlus::seed_from_u64(0); assert_eq!(from_zero, from_sm0); } + + #[test] + fn state_roundtrip() { + let mut rng = Xoshiro128PlusPlus::seed_from_u64(42); + for _ in 0..10 { + rng.next_u32(); + } + let mut clone = Xoshiro128PlusPlus::from_seed(rng.state()); + for _ in 0..10 { + assert_eq!(rng.next_u32(), clone.next_u32()); + } + } } diff --git a/rand_xoshiro/src/xoshiro128starstar.rs b/rand_xoshiro/src/xoshiro128starstar.rs index 9cd978aa..93ea2f6f 100644 --- a/rand_xoshiro/src/xoshiro128starstar.rs +++ b/rand_xoshiro/src/xoshiro128starstar.rs @@ -55,6 +55,8 @@ impl Xoshiro128StarStar { } } +impl_state_array!(Xoshiro128StarStar, u32, 16); + impl SeedableRng for Xoshiro128StarStar { type Seed = [u8; 16]; @@ -145,4 +147,16 @@ mod tests { let from_sm0 = Xoshiro128StarStar::seed_from_u64(0); assert_eq!(from_zero, from_sm0); } + + #[test] + fn state_roundtrip() { + let mut rng = Xoshiro128StarStar::seed_from_u64(42); + for _ in 0..10 { + rng.next_u32(); + } + let mut clone = Xoshiro128StarStar::from_seed(rng.state()); + for _ in 0..10 { + assert_eq!(rng.next_u32(), clone.next_u32()); + } + } } diff --git a/rand_xoshiro/src/xoshiro256plus.rs b/rand_xoshiro/src/xoshiro256plus.rs index 895bdbd6..eae360d8 100644 --- a/rand_xoshiro/src/xoshiro256plus.rs +++ b/rand_xoshiro/src/xoshiro256plus.rs @@ -74,6 +74,8 @@ impl Xoshiro256Plus { } } +impl_state_array!(Xoshiro256Plus, u64, 32); + impl SeedableRng for Xoshiro256Plus { type Seed = [u8; 32]; @@ -150,4 +152,16 @@ mod tests { let from_sm0 = Xoshiro256Plus::seed_from_u64(0); assert_eq!(from_zero, from_sm0); } + + #[test] + fn state_roundtrip() { + let mut rng = Xoshiro256Plus::seed_from_u64(42); + for _ in 0..10 { + rng.next_u64(); + } + let mut clone = Xoshiro256Plus::from_seed(rng.state()); + for _ in 0..10 { + assert_eq!(rng.next_u64(), clone.next_u64()); + } + } } diff --git a/rand_xoshiro/src/xoshiro256plusplus.rs b/rand_xoshiro/src/xoshiro256plusplus.rs index e120a795..2367a2c2 100644 --- a/rand_xoshiro/src/xoshiro256plusplus.rs +++ b/rand_xoshiro/src/xoshiro256plusplus.rs @@ -73,6 +73,8 @@ impl Xoshiro256PlusPlus { } } +impl_state_array!(Xoshiro256PlusPlus, u64, 32); + impl SeedableRng for Xoshiro256PlusPlus { type Seed = [u8; 32]; @@ -149,4 +151,16 @@ mod tests { let from_sm0 = Xoshiro256PlusPlus::seed_from_u64(0); assert_eq!(from_zero, from_sm0); } + + #[test] + fn state_roundtrip() { + let mut rng = Xoshiro256PlusPlus::seed_from_u64(42); + for _ in 0..10 { + rng.next_u64(); + } + let mut clone = Xoshiro256PlusPlus::from_seed(rng.state()); + for _ in 0..10 { + assert_eq!(rng.next_u64(), clone.next_u64()); + } + } } diff --git a/rand_xoshiro/src/xoshiro256starstar.rs b/rand_xoshiro/src/xoshiro256starstar.rs index 236b1364..37de2743 100644 --- a/rand_xoshiro/src/xoshiro256starstar.rs +++ b/rand_xoshiro/src/xoshiro256starstar.rs @@ -73,6 +73,8 @@ impl Xoshiro256StarStar { } } +impl_state_array!(Xoshiro256StarStar, u64, 32); + impl SeedableRng for Xoshiro256StarStar { type Seed = [u8; 32]; @@ -149,4 +151,16 @@ mod tests { let from_sm0 = Xoshiro256StarStar::seed_from_u64(0); assert_eq!(from_zero, from_sm0); } + + #[test] + fn state_roundtrip() { + let mut rng = Xoshiro256StarStar::seed_from_u64(42); + for _ in 0..10 { + rng.next_u64(); + } + let mut clone = Xoshiro256StarStar::from_seed(rng.state()); + for _ in 0..10 { + assert_eq!(rng.next_u64(), clone.next_u64()); + } + } } diff --git a/rand_xoshiro/src/xoshiro512plus.rs b/rand_xoshiro/src/xoshiro512plus.rs index e6499502..8bb484f1 100644 --- a/rand_xoshiro/src/xoshiro512plus.rs +++ b/rand_xoshiro/src/xoshiro512plus.rs @@ -84,6 +84,8 @@ impl Xoshiro512Plus { } } +impl_state_seed512!(Xoshiro512Plus); + impl SeedableRng for Xoshiro512Plus { type Seed = Seed512; @@ -162,4 +164,16 @@ mod tests { let from_sm0 = Xoshiro512Plus::seed_from_u64(0); assert_eq!(from_zero, from_sm0); } + + #[test] + fn state_roundtrip() { + let mut rng = Xoshiro512Plus::seed_from_u64(42); + for _ in 0..10 { + rng.next_u64(); + } + let mut clone = Xoshiro512Plus::from_seed(rng.state()); + for _ in 0..10 { + assert_eq!(rng.next_u64(), clone.next_u64()); + } + } } diff --git a/rand_xoshiro/src/xoshiro512plusplus.rs b/rand_xoshiro/src/xoshiro512plusplus.rs index f95991c9..a39f29cc 100644 --- a/rand_xoshiro/src/xoshiro512plusplus.rs +++ b/rand_xoshiro/src/xoshiro512plusplus.rs @@ -83,6 +83,8 @@ impl Xoshiro512PlusPlus { } } +impl_state_seed512!(Xoshiro512PlusPlus); + impl SeedableRng for Xoshiro512PlusPlus { type Seed = Seed512; @@ -160,4 +162,16 @@ mod tests { let from_sm0 = Xoshiro512PlusPlus::seed_from_u64(0); assert_eq!(from_zero, from_sm0); } + + #[test] + fn state_roundtrip() { + let mut rng = Xoshiro512PlusPlus::seed_from_u64(42); + for _ in 0..10 { + rng.next_u64(); + } + let mut clone = Xoshiro512PlusPlus::from_seed(rng.state()); + for _ in 0..10 { + assert_eq!(rng.next_u64(), clone.next_u64()); + } + } } diff --git a/rand_xoshiro/src/xoshiro512starstar.rs b/rand_xoshiro/src/xoshiro512starstar.rs index e808f6fa..608714ca 100644 --- a/rand_xoshiro/src/xoshiro512starstar.rs +++ b/rand_xoshiro/src/xoshiro512starstar.rs @@ -83,6 +83,8 @@ impl Xoshiro512StarStar { } } +impl_state_seed512!(Xoshiro512StarStar); + impl SeedableRng for Xoshiro512StarStar { type Seed = Seed512; @@ -161,4 +163,16 @@ mod tests { let from_sm0 = Xoshiro512StarStar::seed_from_u64(0); assert_eq!(from_zero, from_sm0); } + + #[test] + fn state_roundtrip() { + let mut rng = Xoshiro512StarStar::seed_from_u64(42); + for _ in 0..10 { + rng.next_u64(); + } + let mut clone = Xoshiro512StarStar::from_seed(rng.state()); + for _ in 0..10 { + assert_eq!(rng.next_u64(), clone.next_u64()); + } + } } From 4ad02d19b56eb3f92a42ae4dc95f2e80fe6287dc Mon Sep 17 00:00:00 2001 From: Vinzent Steinberg Date: Wed, 13 May 2026 00:34:05 +0200 Subject: [PATCH 2/4] rand_xoshiro: Address review comments and bump to 0.8.1 - Simplify `state_roundtrip` tests to a single `assert_eq!(rng, clone)` - Inline `impl_state_scalar!` into `SplitMix64` - Drop redundant `$bytes` argument from `impl_state_pair!` and `impl_state_array_of_four!` and add a `const` size assertion against the type's own layout. - Rename `impl_state_array!` to `impl_state_array_of_four!` and unroll loop --- Cargo.lock | 2 +- rand_xoshiro/CHANGELOG.md | 2 +- rand_xoshiro/Cargo.toml | 2 +- rand_xoshiro/src/common.rs | 50 +++++++++--------------- rand_xoshiro/src/splitmix64.rs | 22 ++++++----- rand_xoshiro/src/xoroshiro128plus.rs | 13 ++---- rand_xoshiro/src/xoroshiro128plusplus.rs | 13 ++---- rand_xoshiro/src/xoroshiro128starstar.rs | 13 ++---- rand_xoshiro/src/xoroshiro64star.rs | 13 ++---- rand_xoshiro/src/xoroshiro64starstar.rs | 13 ++---- rand_xoshiro/src/xoshiro128plus.rs | 13 ++---- rand_xoshiro/src/xoshiro128plusplus.rs | 13 ++---- rand_xoshiro/src/xoshiro128starstar.rs | 13 ++---- rand_xoshiro/src/xoshiro256plus.rs | 13 ++---- rand_xoshiro/src/xoshiro256plusplus.rs | 13 ++---- rand_xoshiro/src/xoshiro256starstar.rs | 13 ++---- rand_xoshiro/src/xoshiro512plus.rs | 11 ++---- rand_xoshiro/src/xoshiro512plusplus.rs | 11 ++---- rand_xoshiro/src/xoshiro512starstar.rs | 11 ++---- 19 files changed, 87 insertions(+), 167 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c3645ade..8efd76d1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -263,7 +263,7 @@ dependencies = [ [[package]] name = "rand_xoshiro" -version = "0.8.0" +version = "0.8.1" dependencies = [ "postcard", "rand_core", diff --git a/rand_xoshiro/CHANGELOG.md b/rand_xoshiro/CHANGELOG.md index f4bfb72a..15d933bc 100644 --- a/rand_xoshiro/CHANGELOG.md +++ b/rand_xoshiro/CHANGELOG.md @@ -4,7 +4,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [Unreleased] +## [0.8.1] - 2026-05-15 ### Added - `state()` method on every RNG type returning the internal state as a value matching `SeedableRng::Seed`, allowing reproduction via `from_seed` without diff --git a/rand_xoshiro/Cargo.toml b/rand_xoshiro/Cargo.toml index 7a734b30..5cf6d2e2 100644 --- a/rand_xoshiro/Cargo.toml +++ b/rand_xoshiro/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rand_xoshiro" -version = "0.8.0" +version = "0.8.1" authors = ["The Rand Project Developers"] license = "MIT OR Apache-2.0" readme = "README.md" diff --git a/rand_xoshiro/src/common.rs b/rand_xoshiro/src/common.rs index c29d96f9..be48dd21 100644 --- a/rand_xoshiro/src/common.rs +++ b/rand_xoshiro/src/common.rs @@ -222,26 +222,9 @@ macro_rules! impl_xoshiro_large { }; } -/// Implement `state`, returning the internal state of a single-word RNG as -/// little-endian bytes that round-trip through [`SeedableRng::from_seed`]. -macro_rules! impl_state_scalar { - ($type:ident) => { - impl $type { - /// Return the internal state of the generator as little-endian - /// bytes that can be passed to [`SeedableRng::from_seed`] to - /// reconstruct an identical generator. - /// - /// [`SeedableRng::from_seed`]: rand_core::SeedableRng::from_seed - pub fn state(&self) -> [u8; 8] { - self.x.to_le_bytes() - } - } - }; -} - /// Implement `state` for an RNG with two word-sized fields `s0` and `s1`. macro_rules! impl_state_pair { - ($type:ident, $word:ty, $bytes:literal) => { + ($type:ident, $word:ty) => { impl $type { /// Return the internal state of the generator as little-endian /// bytes that can be passed to [`SeedableRng::from_seed`] to @@ -253,20 +236,21 @@ macro_rules! impl_state_pair { /// remapped to `seed_from_u64(0)`.) /// /// [`SeedableRng::from_seed`]: rand_core::SeedableRng::from_seed - pub fn state(&self) -> [u8; $bytes] { - let mut out = [0u8; $bytes]; - let n = core::mem::size_of::<$word>(); - out[..n].copy_from_slice(&self.s0.to_le_bytes()); - out[n..].copy_from_slice(&self.s1.to_le_bytes()); + pub fn state(&self) -> [u8; 2 * core::mem::size_of::<$word>()] { + const N: usize = core::mem::size_of::<$word>(); + const { assert!(core::mem::size_of::() == 2 * N); } + let mut out = [0u8; 2 * N]; + out[..N].copy_from_slice(&self.s0.to_le_bytes()); + out[N..].copy_from_slice(&self.s1.to_le_bytes()); out } } }; } -/// Implement `state` for an RNG with a `s: [WORD; N]` field. -macro_rules! impl_state_array { - ($type:ident, $word:ty, $bytes:literal) => { +/// Implement `state` for an RNG with an `s: [WORD; 4]` field. +macro_rules! impl_state_array_of_four { + ($type:ident, $word:ty) => { impl $type { /// Return the internal state of the generator as little-endian /// bytes that can be passed to [`SeedableRng::from_seed`] to @@ -278,12 +262,14 @@ macro_rules! impl_state_array { /// remapped to `seed_from_u64(0)`.) /// /// [`SeedableRng::from_seed`]: rand_core::SeedableRng::from_seed - pub fn state(&self) -> [u8; $bytes] { - let mut out = [0u8; $bytes]; - let n = core::mem::size_of::<$word>(); - for (i, word) in self.s.iter().enumerate() { - out[i * n..(i + 1) * n].copy_from_slice(&word.to_le_bytes()); - } + pub fn state(&self) -> [u8; 4 * core::mem::size_of::<$word>()] { + const N: usize = core::mem::size_of::<$word>(); + const { assert!(core::mem::size_of::() == 4 * N); } + let mut out = [0u8; 4 * N]; + out[..N].copy_from_slice(&self.s[0].to_le_bytes()); + out[N..2 * N].copy_from_slice(&self.s[1].to_le_bytes()); + out[2 * N..3 * N].copy_from_slice(&self.s[2].to_le_bytes()); + out[3 * N..].copy_from_slice(&self.s[3].to_le_bytes()); out } } diff --git a/rand_xoshiro/src/splitmix64.rs b/rand_xoshiro/src/splitmix64.rs index 98dac717..ed70e4ea 100644 --- a/rand_xoshiro/src/splitmix64.rs +++ b/rand_xoshiro/src/splitmix64.rs @@ -60,7 +60,16 @@ impl TryRng for SplitMix64 { } } -impl_state_scalar!(SplitMix64); +impl SplitMix64 { + /// Return the internal state of the generator as little-endian bytes + /// that can be passed to [`SeedableRng::from_seed`] to reconstruct an + /// identical generator. + /// + /// [`SeedableRng::from_seed`]: rand_core::SeedableRng::from_seed + pub fn state(&self) -> [u8; 8] { + self.x.to_le_bytes() + } +} impl SeedableRng for SplitMix64 { type Seed = [u8; 8]; @@ -146,14 +155,9 @@ mod tests { #[test] fn state_roundtrip() { - let mut rng = SplitMix64::seed_from_u64(42); - for _ in 0..10 { - rng.next_u64(); - } - let mut clone = SplitMix64::from_seed(rng.state()); - for _ in 0..10 { - assert_eq!(rng.next_u64(), clone.next_u64()); - } + let rng = SplitMix64::seed_from_u64(42); + let clone = SplitMix64::from_seed(rng.state()); + assert_eq!(clone, rng); } #[test] diff --git a/rand_xoshiro/src/xoroshiro128plus.rs b/rand_xoshiro/src/xoroshiro128plus.rs index f44e2aab..9e886a4b 100644 --- a/rand_xoshiro/src/xoroshiro128plus.rs +++ b/rand_xoshiro/src/xoroshiro128plus.rs @@ -80,7 +80,7 @@ impl TryRng for Xoroshiro128Plus { utils::fill_bytes_via_next_word(dest, || self.try_next_u64()) } } -impl_state_pair!(Xoroshiro128Plus, u64, 16); +impl_state_pair!(Xoroshiro128Plus, u64); impl SeedableRng for Xoroshiro128Plus { type Seed = [u8; 16]; @@ -134,13 +134,8 @@ mod tests { #[test] fn state_roundtrip() { - let mut rng = Xoroshiro128Plus::seed_from_u64(42); - for _ in 0..10 { - rng.next_u64(); - } - let mut clone = Xoroshiro128Plus::from_seed(rng.state()); - for _ in 0..10 { - assert_eq!(rng.next_u64(), clone.next_u64()); - } + let rng = Xoroshiro128Plus::seed_from_u64(42); + let clone = Xoroshiro128Plus::from_seed(rng.state()); + assert_eq!(clone, rng); } } diff --git a/rand_xoshiro/src/xoroshiro128plusplus.rs b/rand_xoshiro/src/xoroshiro128plusplus.rs index a929b2f7..86f3e010 100644 --- a/rand_xoshiro/src/xoroshiro128plusplus.rs +++ b/rand_xoshiro/src/xoroshiro128plusplus.rs @@ -78,7 +78,7 @@ impl TryRng for Xoroshiro128PlusPlus { } } -impl_state_pair!(Xoroshiro128PlusPlus, u64, 16); +impl_state_pair!(Xoroshiro128PlusPlus, u64); impl SeedableRng for Xoroshiro128PlusPlus { type Seed = [u8; 16]; @@ -133,13 +133,8 @@ mod tests { #[test] fn state_roundtrip() { - let mut rng = Xoroshiro128PlusPlus::seed_from_u64(42); - for _ in 0..10 { - rng.next_u64(); - } - let mut clone = Xoroshiro128PlusPlus::from_seed(rng.state()); - for _ in 0..10 { - assert_eq!(rng.next_u64(), clone.next_u64()); - } + let rng = Xoroshiro128PlusPlus::seed_from_u64(42); + let clone = Xoroshiro128PlusPlus::from_seed(rng.state()); + assert_eq!(clone, rng); } } diff --git a/rand_xoshiro/src/xoroshiro128starstar.rs b/rand_xoshiro/src/xoroshiro128starstar.rs index f46c6712..d22d98df 100644 --- a/rand_xoshiro/src/xoroshiro128starstar.rs +++ b/rand_xoshiro/src/xoroshiro128starstar.rs @@ -78,7 +78,7 @@ impl TryRng for Xoroshiro128StarStar { } } -impl_state_pair!(Xoroshiro128StarStar, u64, 16); +impl_state_pair!(Xoroshiro128StarStar, u64); impl SeedableRng for Xoroshiro128StarStar { type Seed = [u8; 16]; @@ -133,13 +133,8 @@ mod tests { #[test] fn state_roundtrip() { - let mut rng = Xoroshiro128StarStar::seed_from_u64(42); - for _ in 0..10 { - rng.next_u64(); - } - let mut clone = Xoroshiro128StarStar::from_seed(rng.state()); - for _ in 0..10 { - assert_eq!(rng.next_u64(), clone.next_u64()); - } + let rng = Xoroshiro128StarStar::seed_from_u64(42); + let clone = Xoroshiro128StarStar::from_seed(rng.state()); + assert_eq!(clone, rng); } } diff --git a/rand_xoshiro/src/xoroshiro64star.rs b/rand_xoshiro/src/xoroshiro64star.rs index d9bf732e..991ef2ce 100644 --- a/rand_xoshiro/src/xoroshiro64star.rs +++ b/rand_xoshiro/src/xoroshiro64star.rs @@ -49,7 +49,7 @@ impl TryRng for Xoroshiro64Star { } } -impl_state_pair!(Xoroshiro64Star, u32, 8); +impl_state_pair!(Xoroshiro64Star, u32); impl SeedableRng for Xoroshiro64Star { type Seed = [u8; 8]; @@ -102,13 +102,8 @@ mod tests { #[test] fn state_roundtrip() { - let mut rng = Xoroshiro64Star::seed_from_u64(42); - for _ in 0..10 { - rng.next_u32(); - } - let mut clone = Xoroshiro64Star::from_seed(rng.state()); - for _ in 0..10 { - assert_eq!(rng.next_u32(), clone.next_u32()); - } + let rng = Xoroshiro64Star::seed_from_u64(42); + let clone = Xoroshiro64Star::from_seed(rng.state()); + assert_eq!(clone, rng); } } diff --git a/rand_xoshiro/src/xoroshiro64starstar.rs b/rand_xoshiro/src/xoroshiro64starstar.rs index e3e1b920..0fe8ce66 100644 --- a/rand_xoshiro/src/xoroshiro64starstar.rs +++ b/rand_xoshiro/src/xoroshiro64starstar.rs @@ -48,7 +48,7 @@ impl TryRng for Xoroshiro64StarStar { } } -impl_state_pair!(Xoroshiro64StarStar, u32, 8); +impl_state_pair!(Xoroshiro64StarStar, u32); impl SeedableRng for Xoroshiro64StarStar { type Seed = [u8; 8]; @@ -101,13 +101,8 @@ mod tests { #[test] fn state_roundtrip() { - let mut rng = Xoroshiro64StarStar::seed_from_u64(42); - for _ in 0..10 { - rng.next_u32(); - } - let mut clone = Xoroshiro64StarStar::from_seed(rng.state()); - for _ in 0..10 { - assert_eq!(rng.next_u32(), clone.next_u32()); - } + let rng = Xoroshiro64StarStar::seed_from_u64(42); + let clone = Xoroshiro64StarStar::from_seed(rng.state()); + assert_eq!(clone, rng); } } diff --git a/rand_xoshiro/src/xoshiro128plus.rs b/rand_xoshiro/src/xoshiro128plus.rs index 60b63229..bb8a9ccd 100644 --- a/rand_xoshiro/src/xoshiro128plus.rs +++ b/rand_xoshiro/src/xoshiro128plus.rs @@ -56,7 +56,7 @@ impl Xoshiro128Plus { } } -impl_state_array!(Xoshiro128Plus, u32, 16); +impl_state_array_of_four!(Xoshiro128Plus, u32); impl SeedableRng for Xoshiro128Plus { type Seed = [u8; 16]; @@ -148,13 +148,8 @@ mod tests { #[test] fn state_roundtrip() { - let mut rng = Xoshiro128Plus::seed_from_u64(42); - for _ in 0..10 { - rng.next_u32(); - } - let mut clone = Xoshiro128Plus::from_seed(rng.state()); - for _ in 0..10 { - assert_eq!(rng.next_u32(), clone.next_u32()); - } + let rng = Xoshiro128Plus::seed_from_u64(42); + let clone = Xoshiro128Plus::from_seed(rng.state()); + assert_eq!(clone, rng); } } diff --git a/rand_xoshiro/src/xoshiro128plusplus.rs b/rand_xoshiro/src/xoshiro128plusplus.rs index d7d2f525..8ae6e25b 100644 --- a/rand_xoshiro/src/xoshiro128plusplus.rs +++ b/rand_xoshiro/src/xoshiro128plusplus.rs @@ -55,7 +55,7 @@ impl Xoshiro128PlusPlus { } } -impl_state_array!(Xoshiro128PlusPlus, u32, 16); +impl_state_array_of_four!(Xoshiro128PlusPlus, u32); impl SeedableRng for Xoshiro128PlusPlus { type Seed = [u8; 16]; @@ -150,13 +150,8 @@ mod tests { #[test] fn state_roundtrip() { - let mut rng = Xoshiro128PlusPlus::seed_from_u64(42); - for _ in 0..10 { - rng.next_u32(); - } - let mut clone = Xoshiro128PlusPlus::from_seed(rng.state()); - for _ in 0..10 { - assert_eq!(rng.next_u32(), clone.next_u32()); - } + let rng = Xoshiro128PlusPlus::seed_from_u64(42); + let clone = Xoshiro128PlusPlus::from_seed(rng.state()); + assert_eq!(clone, rng); } } diff --git a/rand_xoshiro/src/xoshiro128starstar.rs b/rand_xoshiro/src/xoshiro128starstar.rs index 93ea2f6f..b2ce8269 100644 --- a/rand_xoshiro/src/xoshiro128starstar.rs +++ b/rand_xoshiro/src/xoshiro128starstar.rs @@ -55,7 +55,7 @@ impl Xoshiro128StarStar { } } -impl_state_array!(Xoshiro128StarStar, u32, 16); +impl_state_array_of_four!(Xoshiro128StarStar, u32); impl SeedableRng for Xoshiro128StarStar { type Seed = [u8; 16]; @@ -150,13 +150,8 @@ mod tests { #[test] fn state_roundtrip() { - let mut rng = Xoshiro128StarStar::seed_from_u64(42); - for _ in 0..10 { - rng.next_u32(); - } - let mut clone = Xoshiro128StarStar::from_seed(rng.state()); - for _ in 0..10 { - assert_eq!(rng.next_u32(), clone.next_u32()); - } + let rng = Xoshiro128StarStar::seed_from_u64(42); + let clone = Xoshiro128StarStar::from_seed(rng.state()); + assert_eq!(clone, rng); } } diff --git a/rand_xoshiro/src/xoshiro256plus.rs b/rand_xoshiro/src/xoshiro256plus.rs index eae360d8..e83f0f5a 100644 --- a/rand_xoshiro/src/xoshiro256plus.rs +++ b/rand_xoshiro/src/xoshiro256plus.rs @@ -74,7 +74,7 @@ impl Xoshiro256Plus { } } -impl_state_array!(Xoshiro256Plus, u64, 32); +impl_state_array_of_four!(Xoshiro256Plus, u64); impl SeedableRng for Xoshiro256Plus { type Seed = [u8; 32]; @@ -155,13 +155,8 @@ mod tests { #[test] fn state_roundtrip() { - let mut rng = Xoshiro256Plus::seed_from_u64(42); - for _ in 0..10 { - rng.next_u64(); - } - let mut clone = Xoshiro256Plus::from_seed(rng.state()); - for _ in 0..10 { - assert_eq!(rng.next_u64(), clone.next_u64()); - } + let rng = Xoshiro256Plus::seed_from_u64(42); + let clone = Xoshiro256Plus::from_seed(rng.state()); + assert_eq!(clone, rng); } } diff --git a/rand_xoshiro/src/xoshiro256plusplus.rs b/rand_xoshiro/src/xoshiro256plusplus.rs index 2367a2c2..89c9e059 100644 --- a/rand_xoshiro/src/xoshiro256plusplus.rs +++ b/rand_xoshiro/src/xoshiro256plusplus.rs @@ -73,7 +73,7 @@ impl Xoshiro256PlusPlus { } } -impl_state_array!(Xoshiro256PlusPlus, u64, 32); +impl_state_array_of_four!(Xoshiro256PlusPlus, u64); impl SeedableRng for Xoshiro256PlusPlus { type Seed = [u8; 32]; @@ -154,13 +154,8 @@ mod tests { #[test] fn state_roundtrip() { - let mut rng = Xoshiro256PlusPlus::seed_from_u64(42); - for _ in 0..10 { - rng.next_u64(); - } - let mut clone = Xoshiro256PlusPlus::from_seed(rng.state()); - for _ in 0..10 { - assert_eq!(rng.next_u64(), clone.next_u64()); - } + let rng = Xoshiro256PlusPlus::seed_from_u64(42); + let clone = Xoshiro256PlusPlus::from_seed(rng.state()); + assert_eq!(clone, rng); } } diff --git a/rand_xoshiro/src/xoshiro256starstar.rs b/rand_xoshiro/src/xoshiro256starstar.rs index 37de2743..11a51cd5 100644 --- a/rand_xoshiro/src/xoshiro256starstar.rs +++ b/rand_xoshiro/src/xoshiro256starstar.rs @@ -73,7 +73,7 @@ impl Xoshiro256StarStar { } } -impl_state_array!(Xoshiro256StarStar, u64, 32); +impl_state_array_of_four!(Xoshiro256StarStar, u64); impl SeedableRng for Xoshiro256StarStar { type Seed = [u8; 32]; @@ -154,13 +154,8 @@ mod tests { #[test] fn state_roundtrip() { - let mut rng = Xoshiro256StarStar::seed_from_u64(42); - for _ in 0..10 { - rng.next_u64(); - } - let mut clone = Xoshiro256StarStar::from_seed(rng.state()); - for _ in 0..10 { - assert_eq!(rng.next_u64(), clone.next_u64()); - } + let rng = Xoshiro256StarStar::seed_from_u64(42); + let clone = Xoshiro256StarStar::from_seed(rng.state()); + assert_eq!(clone, rng); } } diff --git a/rand_xoshiro/src/xoshiro512plus.rs b/rand_xoshiro/src/xoshiro512plus.rs index 8bb484f1..cbbe5887 100644 --- a/rand_xoshiro/src/xoshiro512plus.rs +++ b/rand_xoshiro/src/xoshiro512plus.rs @@ -167,13 +167,8 @@ mod tests { #[test] fn state_roundtrip() { - let mut rng = Xoshiro512Plus::seed_from_u64(42); - for _ in 0..10 { - rng.next_u64(); - } - let mut clone = Xoshiro512Plus::from_seed(rng.state()); - for _ in 0..10 { - assert_eq!(rng.next_u64(), clone.next_u64()); - } + let rng = Xoshiro512Plus::seed_from_u64(42); + let clone = Xoshiro512Plus::from_seed(rng.state()); + assert_eq!(clone, rng); } } diff --git a/rand_xoshiro/src/xoshiro512plusplus.rs b/rand_xoshiro/src/xoshiro512plusplus.rs index a39f29cc..5d3d8dc4 100644 --- a/rand_xoshiro/src/xoshiro512plusplus.rs +++ b/rand_xoshiro/src/xoshiro512plusplus.rs @@ -165,13 +165,8 @@ mod tests { #[test] fn state_roundtrip() { - let mut rng = Xoshiro512PlusPlus::seed_from_u64(42); - for _ in 0..10 { - rng.next_u64(); - } - let mut clone = Xoshiro512PlusPlus::from_seed(rng.state()); - for _ in 0..10 { - assert_eq!(rng.next_u64(), clone.next_u64()); - } + let rng = Xoshiro512PlusPlus::seed_from_u64(42); + let clone = Xoshiro512PlusPlus::from_seed(rng.state()); + assert_eq!(clone, rng); } } diff --git a/rand_xoshiro/src/xoshiro512starstar.rs b/rand_xoshiro/src/xoshiro512starstar.rs index 608714ca..b81c6411 100644 --- a/rand_xoshiro/src/xoshiro512starstar.rs +++ b/rand_xoshiro/src/xoshiro512starstar.rs @@ -166,13 +166,8 @@ mod tests { #[test] fn state_roundtrip() { - let mut rng = Xoshiro512StarStar::seed_from_u64(42); - for _ in 0..10 { - rng.next_u64(); - } - let mut clone = Xoshiro512StarStar::from_seed(rng.state()); - for _ in 0..10 { - assert_eq!(rng.next_u64(), clone.next_u64()); - } + let rng = Xoshiro512StarStar::seed_from_u64(42); + let clone = Xoshiro512StarStar::from_seed(rng.state()); + assert_eq!(clone, rng); } } From d21eab0e82ba6316ddd38ade3cde77540067f45b Mon Sep 17 00:00:00 2001 From: Vinzent Steinberg Date: Wed, 13 May 2026 00:57:18 +0200 Subject: [PATCH 3/4] rand_xoshiro: Apply rustfmt --- rand_xoshiro/src/common.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/rand_xoshiro/src/common.rs b/rand_xoshiro/src/common.rs index be48dd21..d92f6e6e 100644 --- a/rand_xoshiro/src/common.rs +++ b/rand_xoshiro/src/common.rs @@ -238,7 +238,9 @@ macro_rules! impl_state_pair { /// [`SeedableRng::from_seed`]: rand_core::SeedableRng::from_seed pub fn state(&self) -> [u8; 2 * core::mem::size_of::<$word>()] { const N: usize = core::mem::size_of::<$word>(); - const { assert!(core::mem::size_of::() == 2 * N); } + const { + assert!(core::mem::size_of::() == 2 * N); + } let mut out = [0u8; 2 * N]; out[..N].copy_from_slice(&self.s0.to_le_bytes()); out[N..].copy_from_slice(&self.s1.to_le_bytes()); @@ -264,7 +266,9 @@ macro_rules! impl_state_array_of_four { /// [`SeedableRng::from_seed`]: rand_core::SeedableRng::from_seed pub fn state(&self) -> [u8; 4 * core::mem::size_of::<$word>()] { const N: usize = core::mem::size_of::<$word>(); - const { assert!(core::mem::size_of::() == 4 * N); } + const { + assert!(core::mem::size_of::() == 4 * N); + } let mut out = [0u8; 4 * N]; out[..N].copy_from_slice(&self.s[0].to_le_bytes()); out[N..2 * N].copy_from_slice(&self.s[1].to_le_bytes()); From a9ca2ac7e1e2f7b1a16e5e40d1e24d71b8ce90cf Mon Sep 17 00:00:00 2001 From: Vinzent Steinberg Date: Sat, 16 May 2026 14:45:16 +0200 Subject: [PATCH 4/4] rand_xoshiro: Update 0.8.1 release to today --- rand_xoshiro/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rand_xoshiro/CHANGELOG.md b/rand_xoshiro/CHANGELOG.md index 15d933bc..a360b3c8 100644 --- a/rand_xoshiro/CHANGELOG.md +++ b/rand_xoshiro/CHANGELOG.md @@ -4,7 +4,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [0.8.1] - 2026-05-15 +## [0.8.1] - 2026-05-16 ### Added - `state()` method on every RNG type returning the internal state as a value matching `SeedableRng::Seed`, allowing reproduction via `from_seed` without