Note: The Listed platform will permanently shut down December 31, 2026. Your data published on Listed will still be available in your personal Standard Notes account. Learn more

Random in Rust with stdt

          __      .___ __   
  _______/  |_  __| _//  |_ 
 /  ___/\   __\/ __ |\   __\
 \___ \  |  | / /_/ | |  |  
/____  > |__| \____ | |__|  
     \/ 

stdt on GitHub |

stdt on crates.io

The stdt Rust library (v0.0.5) now provides minimal, non-cryptographic PRNG utilities for generating integers, floating-point values, and random selections. Seeds are derived from the current time in nanoseconds combined with a hashed thread ID. Inputs are mixed with a SplitMix-inspired multiply-rotate scheme, and the core PRNG is xorshift64*, chosen for speed and a tiny state. Not suitable for cryptographic use, but small state, zero dependencies, no unsafe, and predictable costs per call..

The seeding path combines now_ns() and a hashed thread_id() via the mixer function. The mixer performs a multiply–xor–rotate–multiply sequence using constants aligned with SplitMix-style diffusion.

 fn mixer(h: &mut u64, x: u64) {
    *h ^= x.wrapping_mul(0x9E37_79B9_7F4A_7C15);
    *h = (*h).rotate_left(27).wrapping_mul(0x94D0_49BB_1331_11EB);
}

The PRNG core itself is the standard xorshift64*. This gives a period of 264 − 1 for nonzero state, and adequate bit-mixing for non-adversarial use.

fn prng(state: &mut u64) -> u64 {
    let mut x = *state;
    x ^= x >> 12;
    x ^= x << 25;
    x ^= x >> 27;
    *state = x;
    x.wrapping_mul(0x2545_F491_4F6C_DD1D)
}

Integer generation handles signed, inclusive intervals correctly by mapping i128 to a monotone u128 line through a sign-bit XOR. The decision to force the state away from zero prevents the well-known xorshift zero-state lockup.

fn generator_u128(thread_id: u64, ts_ns: u128) -> u128 {
    let mut h: u64 = 0x9E37_79B9_7F4A_7C15;
    mixer(&mut h, thread_id);
    mixer(&mut h, (ts_ns >> 64) as u64);
    mixer(&mut h, ts_ns as u64);
    let mut state = if h == 0 { 1 } else { h };

    // Extend to 16 byte
    let mut bytes = [0u8; 16];
    bytes[..8].copy_from_slice(&prng(&mut state).to_be_bytes());
    bytes[8..].copy_from_slice(&prng(&mut state).to_be_bytes());

    u128::from_be_bytes(bytes)
}

Integer sampling uses start + (seed % width) and maps back with a sign mask. Floating sampling honors f64 precision by extracting the top 53 bits from the u128 seed, forming a unit value in [0, 1) as mant, and then scaling: start + (end - start) * unit. This gives the best possible granularity a f64 can represent.

The selection helpers are straightforward: choose samples an index over [0, n−1] and returns a reference, while choose_iter materializes the iterable into a Vec and selects, which is constant time on a Vec and keeps the implementation simple for arbitrary IntoIterator.

The design avoids global state or synchronization; seeding is per call, and the PRNG body is a handful of ALU ops. There are weaknesses, but they are not material to the stated scope.

  • First, the seeding relies on time and thread identity, so calls occurring on the same thread within the same effective timestamp can collide; many systems do not deliver true nanosecond granularity, so this kind of duplicates are possible.
  • Second, integer sampling uses a modulo reduction seed % width `which introduces small bias unless the width divides the domain; this is negligible for casual utilities but should be acknowledged.
  • Third, the u128 seed is built from two successive outputs of a single 64-bit state; it does not uniformly cover 2128, which is fine here but worth noting.
  • Fourth, the seeding path is predictably derivable to an attacker (time plus hashed thread id via DefaultHasher), consistent with the explicit disclaimer “Not cryptographically secure.”
  • Fifth, choose_iter allocates a vector; for very large or streaming sources, a one-pass reservoir sampler would be more memory-friendly.

If requirements grow beyond “minimal, non-crypto,” consider:

  • a thread-local PRNG state seeded once per thread to remove timestamp/hash overhead on hot paths and eliminate same-timestamp collisions;
  • replacing modulo mapping with a multiply-high technique to reduce bias without rejection; and
  • switching choose_iter to reservoir sampling to avoid allocation while still returning exactly one item when available.

stdt on GitHub |

stdt on crates.io


You'll only receive email when they publish something new.

More from GSLF
All posts