{"id":51514731,"url":"https://github.com/jamesgober/mod-rand","last_synced_at":"2026-07-08T10:01:45.341Z","repository":{"id":357296080,"uuid":"1236042918","full_name":"jamesgober/mod-rand","owner":"jamesgober","description":"Tiered random number generation for Rust. Fast PRNG, process-unique seeds, and OS-backed cryptographic random in one zero-dependency library. Pick the tier appropriate to your threat model.","archived":false,"fork":false,"pushed_at":"2026-05-12T06:58:51.000Z","size":95,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-12T08:08:42.699Z","etag":null,"topics":["cryptography","no-std","prng","random","random-number-generator","rng","rust","rust-library","xoshiro","zero-dependencies"],"latest_commit_sha":null,"homepage":null,"language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jamesgober.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-05-11T22:38:03.000Z","updated_at":"2026-05-12T06:56:22.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/jamesgober/mod-rand","commit_stats":null,"previous_names":["jamesgober/mod-rand"],"tags_count":4,"template":false,"template_full_name":null,"purl":"pkg:github/jamesgober/mod-rand","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jamesgober%2Fmod-rand","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jamesgober%2Fmod-rand/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jamesgober%2Fmod-rand/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jamesgober%2Fmod-rand/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jamesgober","download_url":"https://codeload.github.com/jamesgober/mod-rand/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jamesgober%2Fmod-rand/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35260671,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-07-08T02:00:06.796Z","response_time":61,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cryptography","no-std","prng","random","random-number-generator","rng","rust","rust-library","xoshiro","zero-dependencies"],"created_at":"2026-07-08T10:01:43.099Z","updated_at":"2026-07-08T10:01:45.293Z","avatar_url":"https://github.com/jamesgober.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003ch1 align=\"center\"\u003e\n  \u003cimg width=\"99\" alt=\"Rust logo\" src=\"https://raw.githubusercontent.com/jamesgober/rust-collection/72baabd71f00e14aa9184efcb16fa3deddda3a0a/assets/rust-logo.svg\"\u003e\n  \u003cbr\u003e\n  \u003ccode\u003emod-rand\u003c/code\u003e\n  \u003cbr\u003e\n  \u003csup\u003e\n    \u003csub\u003eTIERED RANDOMNESS FOR RUST\u003c/sub\u003e\n  \u003c/sup\u003e\n\u003c/h1\u003e\n\n\u003cp align=\"center\"\u003e\n    \u003ca href=\"https://crates.io/crates/mod-rand\"\u003e\u003cimg alt=\"crates.io\" src=\"https://img.shields.io/crates/v/mod-rand.svg\"\u003e\u003c/a\u003e\n    \u003ca href=\"https://crates.io/crates/mod-rand\"\u003e\u003cimg alt=\"downloads\" src=\"https://img.shields.io/crates/d/mod-rand.svg\"\u003e\u003c/a\u003e\n    \u003ca href=\"https://docs.rs/mod-rand\"\u003e\u003cimg alt=\"docs.rs\" src=\"https://docs.rs/mod-rand/badge.svg\"\u003e\u003c/a\u003e\n    \u003cimg alt=\"MSRV\" src=\"https://img.shields.io/badge/MSRV-1.75%2B-blue.svg?style=flat-square\" title=\"Rust Version\"\u003e\n    \u003ca href=\"https://github.com/jamesgober/mod-rand/actions/workflows/ci.yml\"\u003e\u003cimg alt=\"CI\" src=\"https://github.com/jamesgober/mod-rand/actions/workflows/ci.yml/badge.svg\"\u003e\u003c/a\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n    Numbers, bytes, strings, tokens, and collection ops (shuffle, sample,\u003cbr\u003e\n    weighted choice) at three explicit threat-model tiers — one zero-dependency library.\n\u003c/p\u003e\n\n---\n\n## What it is\n\n`mod-rand` is a tiered **randomness** library for Rust. Three concrete\nrandom sources, each chosen to be excellent for one threat model;\nevery operation you'd reach for — bounded integer ranges, byte\nbuffers, hex tokens, random or process-unique strings, shuffles,\nsamples, and weighted choice — exposed at the appropriate tier.\n\nZero runtime dependencies. MSRV `1.75`. API locked under SemVer\nfor the entire `1.x` line.\n\n```rust\nuse mod_rand::tier1::Xoshiro256;\nuse mod_rand::{tier2, tier3};\n\n// Tier 1 — fast deterministic PRNG (xoshiro256**). Reproducible from a seed.\nlet mut rng = Xoshiro256::seed_from_u64(42);\nlet n: u64       = rng.next_u64();\nlet pct: u32     = rng.gen_range_u32(0..100);\nlet token: String = rng.gen_alphanumeric(16);\n\n// Tier 2 — process-unique values. Tempdir names, request IDs.\nlet id: String = tier2::unique_name(12);\n\n// Tier 3 — OS-backed cryptographic random. Tokens, keys, session IDs.\nlet secret: String = tier3::random_hex(32)?;\n# Ok::\u003c(), std::io::Error\u003e(())\n```\n\n## What it covers\n\nEvery category of randomness operation that's in scope, at every tier\nwhere it makes sense.\n\n| Operation                              | Tier 1 (PRNG)      | Tier 2 (process-unique) | Tier 3 (crypto) |\n|----------------------------------------|--------------------|--------------------------|-----------------|\n| Raw `u32` / `u64` draws                | `next_u32` / `next_u64` | `unique_u64`            | `random_u32` / `random_u64` |\n| Fill a `\u0026mut [u8]`                     | `fill_bytes`       | —                        | `fill_bytes`     |\n| Bounded integer ranges (every width)   | `gen_range_*`      | `range_*`                | `random_range_*` |\n| 128-bit integer ranges                 | `gen_range_u128` / `i128` | `range_u128` / `i128` | `random_range_u128` / `i128` |\n| Uniform `f64` / `f32`                  | `gen_f64` / `gen_f32` / `next_f64` | —                | —               |\n| Bernoulli trial                        | `gen_bool(p)`      | —                        | —               |\n| Hex strings                            | `gen_hex(len)`     | `unique_hex(len)` / `random_hex_string(len)` | `random_hex(bytes)` / `random_hex_string(len)` |\n| Alphanumeric strings                   | `gen_alphanumeric` | `unique_name` / `random_alphanumeric` | `random_alphanumeric` |\n| Arbitrary-charset strings              | `gen_string`       | `random_string`          | `random_string` |\n| Base32 / base58 / base64 / URL-safe    | via `charsets::*`  | via `charsets::*`        | via `charsets::*` |\n| Shuffle a `\u0026mut [T]`                   | `shuffle`          | —                        | `shuffle`        |\n| Sample `k` without replacement         | `sample`           | —                        | —               |\n| Weighted choice                        | `weighted_choice` / `weighted_index` | —          | —               |\n| Stream splitting (`2^128` / `2^192`)   | `jump` / `long_jump` | —                      | —               |\n\nTier 1 is `no_std`-compatible for everything that doesn't require\nallocation. Tier 2 and Tier 3 require `std` (default feature).\n\n## The three tiers\n\nPick by threat model, not by speed.\n\n| Tier | Algorithm | Use case | Crypto-safe |\n|------|-----------|----------|-------------|\n| 1 | xoshiro256\\*\\* (splitmix64-seeded) | Simulation, fixtures, shuffling, non-security strings | No |\n| 2 | PID + nanos + atomic counter + Stafford-mix-13 | Tempdir names, request IDs, log correlation IDs | No |\n| 3 | OS syscall (`getrandom` / `BCryptGenRandom` / `getentropy`) | Tokens, keys, session IDs, password salts | Yes |\n\n| Question                                           | Tier |\n|----------------------------------------------------|------|\n| Is the value reproducible from a seed?             | 1    |\n| Must two calls inside one process always differ?   | 2    |\n| Could an attacker benefit from predicting it?      | 3    |\n\nIf the answer to the third question is \"yes\" — even maybe — use Tier 3.\n\n## Bounded ranges — every integer width\n\nEvery tier exposes `gen_range_*` / `range_*` / `random_range_*`\nmethods using Rust's range syntax. `..` is half-open, `..=` is\ninclusive; the caller's choice is the contract. All bounded methods\nuse **Daniel Lemire's \"Nearly Divisionless\"** rejection sampling, so\noutput is uniformly distributed — no modulo bias.\n\nWidths covered: `u8`, `u16`, `u32`, `u64`, `u128`, `usize`, and the\nsigned counterparts.\n\n```rust\nuse mod_rand::tier1::Xoshiro256;\nuse mod_rand::{tier2, tier3};\n\nlet mut rng = Xoshiro256::seed_from_u64(42);\n\nlet byte: u8   = rng.gen_range_inclusive_u8(0..=255);\nlet pct:  u32  = rng.gen_range_u32(0..100);\nlet big:  u128 = rng.gen_range_u128(0..(u128::MAX / 2));\nlet die:  u32  = rng.gen_range_inclusive_u32(1..=6);   // classic d6\n\nlet id     = tier2::range_inclusive_u32(1..=1_000);\nlet secret = tier3::random_range_inclusive_u64(0..=u64::MAX)?;\n# Ok::\u003c(), std::io::Error\u003e(())\n```\n\nThe 128-bit ranges generalise Lemire's algorithm to a 256-bit\nintermediate via two `u64` draws; the full-width inclusive\n`0..=u128::MAX` is special-cased to a raw 128-bit reinterpret.\n\n## String generation\n\nCustom charset, or a pick from the standard ones in\n[`mod_rand::charsets`](https://docs.rs/mod-rand/latest/mod_rand/charsets/index.html).\n\n```rust\nuse mod_rand::tier1::Xoshiro256;\nuse mod_rand::{charsets, tier2, tier3};\n\nlet mut rng = Xoshiro256::seed_from_u64(1);\n\n// Tier 1 — deterministic from the seed.\nlet id    = rng.gen_alphanumeric(16);\nlet hex   = rng.gen_hex(32);\nlet pin   = rng.gen_numeric(6);\n\n// Custom charset — pick from charsets:: or supply your own \u0026[u8].\nlet url   = rng.gen_string(24, charsets::URL_SAFE);\nlet coin  = rng.gen_string(20, charsets::BASE58);\n\n// Tier 2 — `unique_*` guarantees per-call distinctness;\n// `random_*` guarantees a uniform distribution. Both ship.\nlet name  = tier2::unique_name(12);             // distinct across calls\nlet token = tier2::random_alphanumeric(16);     // uniform distribution\n\n// Tier 3 — cryptographic.\nlet key   = tier3::random_alphanumeric(32)?;\n# Ok::\u003c(), std::io::Error\u003e(())\n```\n\nCharset constants in `mod_rand::charsets`: `ALPHANUMERIC`, `ALPHA`,\n`ALPHA_LOWER`, `ALPHA_UPPER`, `NUMERIC`, `HEX_LOWER`, `HEX_UPPER`,\n`URL_SAFE` (RFC 4648 §5), `BASE58` (Bitcoin alphabet), `BASE64`\n(RFC 4648 §4). Custom charsets are accepted as `\u0026[u8]`; non-ASCII\ncharsets are rejected (panic on Tier 1 / Tier 2; `InvalidInput` on\nTier 3) so the returned `String` is always valid UTF-8.\n\n## Collection operations\n\nFisher-Yates shuffle, sample-without-replacement, and weighted\nchoice. Available on Tier 1 (deterministic, reproducible) and\nTier 3 shuffle (cryptographic).\n\n```rust\nuse mod_rand::tier1::Xoshiro256;\nuse mod_rand::tier3;\n\nlet mut rng = Xoshiro256::seed_from_u64(1);\n\n// Shuffle in place. Uniform over all n! permutations.\nlet mut hand: Vec\u003cu8\u003e = (0..52).collect();\nrng.shuffle(\u0026mut hand);\n\n// Sample k references without replacement, in source-slice order.\nlet pool: Vec\u003c\u0026str\u003e = vec![\"alice\", \"bob\", \"carol\", \"dave\", \"erin\"];\nlet chosen: Vec\u003c\u0026\u0026str\u003e = rng.sample(\u0026pool, 2);\n\n// Weighted choice over a probability vector.\nlet items   = [\"common\", \"uncommon\", \"rare\", \"epic\"];\nlet weights = [70.0, 20.0, 8.0, 2.0];\nlet drop    = rng.weighted_choice(\u0026items, \u0026weights).unwrap();\n\n// Cryptographic shuffle — every permutation drawn from the OS CSPRNG.\nlet mut deck: Vec\u003cu8\u003e = (0..52).collect();\ntier3::shuffle(\u0026mut deck)?;\n# Ok::\u003c(), std::io::Error\u003e(())\n```\n\n## Performance\n\nMeasured on a Ryzen 9 9950X3D, Windows 11 (`cargo bench`):\n\n**Tier 1 — xoshiro256\\*\\* (target ~1 ns/u64)**\n\n| op                                              | time      |\n|-------------------------------------------------|-----------|\n| `next_u64`                                      | ~0.6 ns   |\n| `next_f64` / `gen_f64`                          | ~0.7 ns   |\n| `gen_f32`                                       | ~0.7 ns   |\n| `gen_bool(0.5)`                                 | ~0.8 ns   |\n| `fill_bytes(32)`                                | ~2.0 ns   |\n| `gen_range_u64(0..100)`                         | ~0.8 ns   |\n| `gen_range_inclusive_u32(1..=6)` (d6)           | ~0.9 ns   |\n| `gen_range_u8(0..100)`                          | ~0.8 ns   |\n| `gen_range_u128(0..u128::MAX/2)`                | ~2.2 ns   |\n| `gen_range_inclusive_u128(0..=u128::MAX)`       | ~1.1 ns   |\n| `gen_range_u64(0..⅔·u64::MAX)` worst-case       | ~5.9 ns   |\n| `gen_alphanumeric(16)`                          | ~58 ns    |\n| `gen_alphanumeric(64)`                          | ~115 ns   |\n| `gen_hex(32)`                                   | ~63 ns    |\n| `shuffle(10)`                                   | ~10 ns    |\n| `shuffle(100)`                                  | ~104 ns   |\n| `shuffle(1000)`                                 | ~953 ns   |\n| `shuffle(10000)`                                | ~9.5 µs   |\n| `sample(k=10, n=1000)`                          | ~905 ns   |\n| `weighted_index(10 weights)`                    | ~19 ns    |\n| `weighted_index(100 weights)`                   | ~172 ns   |\n| `weighted_index(1000 weights)`                  | ~1.1 µs   |\n\n**Tier 2 — process-unique (dominated by `SystemTime::now()`)**\n\n| op                                              | time      |\n|-------------------------------------------------|-----------|\n| `unique_u64`                                    | ~21 ns    |\n| `unique_name(8)`                                | ~42 ns    |\n| `unique_hex(16)`                                | ~47 ns    |\n| `range_u64(0..100)`                             | ~23 ns    |\n| `range_u128(0..u128::MAX/2)`                    | ~45 ns    |\n| `random_alphanumeric(16)`                       | ~420 ns   |\n\n**Tier 3 — OS CSPRNG (Windows / `BCryptGenRandom`)**\n\n| op                                              | time      |\n|-------------------------------------------------|-----------|\n| `random_u64`                                    | ~35 ns    |\n| `fill_bytes(32)`                                | ~53 ns    |\n| `fill_bytes(1024)`                              | ~224 ns   |\n| `random_hex(16)`                                | ~97 ns    |\n| `random_range_u64(0..100)`                      | ~35 ns    |\n| `random_range_u128(0..u128::MAX/2)`             | ~48 ns    |\n| `random_alphanumeric(16)`                       | ~628 ns   |\n| `random_alphanumeric(64)`                       | ~2.4 µs   |\n| `shuffle(52)` (deck of cards)                   | ~1.9 µs   |\n| `shuffle(1000)`                                 | ~38 µs    |\n\nTier 1 beats its 1 ns/u64 target. Linux / macOS Tier 3 numbers are\nkernel-dependent; expect 100–500 ns per call on commodity hardware.\n\n## Why this library exists\n\n- **Zero dependencies.** No `rand`, no `getrandom` crate, no `libc`.\n  Just `std`. Tier 1 works in `no_std`.\n- **Explicit threat model.** You pick the tier; you know what\n  guarantees you're getting.\n- **Lower MSRV than the alternatives.** `1.75`. Many random crates\n  in the ecosystem require `1.85+`.\n- **Determinism contract on Tier 1.** Locked byte-for-byte under\n  SemVer for the entire `1.x` line — see\n  [`docs/STABILITY.md`](docs/STABILITY.md).\n- **One library, three tiers, every operation.** Numbers, bytes,\n  strings, tokens, shuffle, sample, weighted choice. You don't need\n  `rand` + `rand_distr` + `fastrand` + a hand-rolled crypto random.\n\n## How it compares\n\nHonest, side-by-side. The full discussion (including non-strengths)\nis in [`docs/COMPARISON.md`](docs/COMPARISON.md).\n\n|                              | `mod-rand` | `rand` + `rand_distr` | `fastrand` | `nanorand` |\n|------------------------------|------------|-----------------------|------------|------------|\n| Fast PRNG                    | ✓ Tier 1   | ✓ many                | ✓          | ✓ many     |\n| OS cryptographic random      | ✓ Tier 3   | ✓ via `getrandom`     | ✗          | ✓ ChaCha20 (userspace) |\n| Process-unique stream        | ✓ Tier 2   | partial (build yourself) | ✗       | ✗          |\n| 128-bit integer ranges       | ✓          | ✓                     | ✗          | ✗          |\n| String generation in API     | ✓          | via `Alphanumeric`    | ✗          | ✗          |\n| Shuffle / sample / weighted  | ✓          | ✓                     | shuffle    | shuffle    |\n| Named distributions          | ✗          | ✓ (`rand_distr`)      | ✗          | ✗          |\n| Generic `Rng` trait          | ✗          | ✓                     | ✗          | partial    |\n| Runtime crate dependencies   | **0**      | several (`getrandom`, etc.) | 0+ | 0+         |\n| MSRV (at time of writing)    | **1.75**   | higher                | 1.61       | 1.65       |\n\nUse `mod-rand` when its tier model fits and you want zero deps + a\nlocked API. Reach for `rand` if you need named distributions or\ngeneric-trait ecosystem interop.\n\n## Feature flags\n\n```toml\n[dependencies]\nmod-rand = { version = \"1.0\", default-features = false }   # tier1 only, no_std\nmod-rand = { version = \"1.0\", features = [\"tier2\"] }       # + tier2\nmod-rand = \"1.0\"                                             # all three tiers (default)\n```\n\n| Feature  | Effect                                                         |\n|----------|----------------------------------------------------------------|\n| `std`    | Required by `tier2`, `tier3`, and string generation on `tier1` |\n| `tier2`  | Enables `mod_rand::tier2`                                      |\n| `tier3`  | Enables `mod_rand::tier3`                                      |\n\nDefault features: `[\"std\", \"tier2\", \"tier3\"]`.\n\n## Documentation\n\n- [docs/API.md](docs/API.md) — hand-written API reference, tier by tier.\n- [docs/STABILITY.md](docs/STABILITY.md) — SemVer policy, determinism\n  contract, deprecation rules.\n- [docs/COMPARISON.md](docs/COMPARISON.md) — side-by-side with `rand`,\n  `fastrand`, `nanorand`.\n- [docs/API-FREEZE-AUDIT.md](docs/API-FREEZE-AUDIT.md) — full manifest\n  of every `1.0` public symbol.\n- [CHANGELOG.md](CHANGELOG.md) — release history.\n\n## Minimum supported Rust version\n\n`1.75` — pinned in `Cargo.toml`, verified by CI on every change. An\nMSRV bump is a minor-version bump minimum.\n\n## License\n\nApache-2.0. See [LICENSE](LICENSE).\n\n\n\u003c!-- COPYRIGHT\n----------------------------------\u003e\n\u003cdiv align=\"center\"\u003e\n  \u003cbr\u003e\n  \u003ch2\u003e\u003c/h2\u003e\n  Copyright \u0026copy; 2026 James Gober.\n\u003c/div\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjamesgober%2Fmod-rand","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjamesgober%2Fmod-rand","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjamesgober%2Fmod-rand/lists"}