rand_xoshiro: Add state() for serde-free state export#110
Conversation
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.
956d473 to
76737e8
Compare
state() for serde-free state export (#109)state() for serde-free state export
dhardy
left a comment
There was a problem hiding this comment.
A few comments on style, otherwise this looks fine.
You might as well bump the version number to prepare the a patch release (unless you'd prefer to make a new PR for that).
| for _ in 0..10 { | ||
| rng.next_u64(); | ||
| } | ||
| let mut clone = SplitMix64::from_seed(rng.state()); |
There was a problem hiding this comment.
Since the generator impls Eq we should do assert_eq!(clone, rng).
| pub fn state(&self) -> [u8; $bytes] { | ||
| let mut out = [0u8; $bytes]; | ||
| let n = core::mem::size_of::<$word>(); |
There was a problem hiding this comment.
We should be able to use core::mem::size_of::<$word>() * 2 instead of $bytes here.
Use const N: usize = core::mem::size_of::<u64>() if you want a const binding inside the fn.
|
|
||
| /// Implement `state` for an RNG with a `s: [WORD; N]` field. | ||
| macro_rules! impl_state_array { | ||
| ($type:ident, $word:ty, $bytes:literal) => { |
There was a problem hiding this comment.
I think it would be more intuitive to pass $N (size in words) instead of $bytes? Unimportant.
Actually, since N == 4 in all uses, it would be better to rename this impl_state_array_of_four and unroll the loop below.
| pub fn state(&self) -> [u8; $bytes] { | ||
| let mut out = [0u8; $bytes]; |
There was a problem hiding this comment.
We can add:
const {
assert!(core::mem::size_of::<Self>, $bytes);
}
| /// 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 { |
There was a problem hiding this comment.
This is only used once so should be inlined.
Disclaimer: This PR is mostly Claude generated.
Add a
state()method to all 15 RNG types inrand_xoshiro, returning the internal state as a value matching the type'sSeedableRng::Seed. This lets callers persist and reload generator state without enabling theserdefeature:Self::from_seed(rng.state())reconstructs an identical generator.Return type per RNG matches its
Seed:state()SplitMix64,Xoroshiro64Star,Xoroshiro64StarStar[u8; 8]Xoroshiro128*,Xoshiro128*[u8; 16]Xoshiro256*[u8; 32]Xoshiro512*Seed512The 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_seedremaps an all-zero input toseed_from_u64(0); this is documented on eachstate().)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 onestate_roundtriptest.Fixes #109, #64.