Fast PRNG, process-unique seeds, and OS-backed cryptographic random
in one zero-dependency library. Pick the tier appropriate to your threat model.
Random number generation in Rust today forces a choice: pull in the
heavy rand ecosystem (multiple crates, opinionated traits, generic
overhead) or write your own. mod-rand is the middle ground — three
clearly-tiered random sources in one library, zero external
dependencies, MSRV 1.75.
use mod_rand::{tier1, tier2, tier3};
// Tier 1: Fast deterministic PRNG — for simulations and test fixtures.
let mut rng = tier1::Xoshiro256::seed_from_u64(42);
let n: u64 = rng.next_u64();
// Tier 2: Process-unique seeds — for tempdir names and request IDs.
let name: String = tier2::unique_name(8);
// Tier 3: Cryptographic random — for tokens and keys.
let token: String = tier3::random_hex(16)?;
# Ok::<(), std::io::Error>(())Bounded ranges — every tier exposes a parallel family of bounded-range
methods using Rust's range syntax (.. half-open, ..= inclusive).
All variants use Lemire's "Nearly Divisionless" rejection sampling, so
output is uniformly distributed with no modulo bias:
use mod_rand::tier1::Xoshiro256;
use mod_rand::{tier2, tier3};
let mut rng = Xoshiro256::seed_from_u64(42);
let pct: u32 = rng.gen_range_u32(0..100); // [0, 100)
let die: u32 = rng.gen_range_inclusive_u32(1..=6); // [1, 6]
let id = tier2::range_inclusive_u32(1..=1_000);
let secret = tier3::random_range_inclusive_u64(0..=u64::MAX)?;
# Ok::<(), std::io::Error>(())| Tier | Algorithm | Use case | Crypto-safe |
|---|---|---|---|
| 1 | xoshiro256** (splitmix64-seeded) | Simulation, fixtures, shuffling | No |
| 2 | PID + nanos + counter + Stafford-mix-13 | Tempdir names, request IDs | No |
| 3 | OS syscall (getrandom/BCryptGenRandom/getentropy) |
Tokens, keys, session IDs | Yes |
Microbenchmarked on x86_64 (cargo bench):
| Op | Tier 1 | Tier 2 | Tier 3 (Windows) |
|---|---|---|---|
| Single 64-bit value | ~0.6 ns | ~20 ns | ~35 ns |
| 32 random bytes | ~2 ns | — | ~55 ns |
| 16-byte hex token | — | ~45 ns | ~100 ns |
Bounded gen_range_u64(0..100) |
~0.9 ns | ~22 ns | ~35 ns |
Die roll ..=6 (worst-case bias trap) |
~0.9 ns | ~22 ns | ~35 ns |
Tier 1 hits ~0.6 ns/u64 — better than the 1 ns/u64 target. Tier 3 latency on Linux/macOS is kernel-dependent; expect 100–500 ns.
- Zero dependencies. No
rand, nogetrandomcrate, nolibc. Juststd. Tier 1 even works inno_std. - Explicit threat model. You pick the tier; you know what guarantees you're getting.
- Lower MSRV than the alternatives. Works on Rust 1.75; many random crates today require 1.85+.
- Fast. Tier 1 is ~0.6 ns/u64. Tier 2 is ~20 ns. Tier 3 is one syscall.
[dependencies]
mod-rand = { version = "0.9.5", default-features = false } # tier1 only, no_std
mod-rand = { version = "0.9.5", features = ["tier2"] } # + process-unique
mod-rand = "0.9.5" # all three tiers (default)The 0.9.x line ships the real algorithms — full xoshiro256** with
splitmix64 seeding (Tier 1), Stafford-mix-13 over PID + nanos +
atomic counter (Tier 2), and direct platform syscalls
(getrandom(2) / BCryptGenRandom / getentropy(3)) for Tier 3.
The API is stable through the 0.9.x series; 1.0 will pin it.
For tier-by-tier semantics, performance targets, and guarantees, see docs/API.md.
1.75 — pinned in Cargo.toml and verified by CI.
Apache-2.0. See LICENSE.
Copyright © 2026 James Gober.