vrd 0.0.12

A lightweight, no_std-friendly random number generator backed by Xoshiro256++ with optional Mersenne Twister support.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
<!-- SPDX-FileCopyrightText: 2023-2026 vrd contributors -->
<!-- SPDX-License-Identifier: Apache-2.0 OR MIT -->

<p align="center">
  <img src="https://cloudcdn.pro/vrd/v1/logos/vrd.svg" alt="vrd logo" width="128" />
</p>

<h1 align="center">vrd</h1>

<p align="center">
  A lightweight, <code>no_std</code>-friendly random number
  generator for Rust - Xoshiro256++ by default, with optional
  Mersenne&nbsp;Twister, PCG, ChaCha20&nbsp;CSPRNG, SIMD
  bulk-byte, and quasi-random backends.
</p>

<p align="center">
  <a href="https://github.com/sebastienrousseau/vrd/actions"><img src="https://img.shields.io/github/actions/workflow/status/sebastienrousseau/vrd/ci.yml?style=for-the-badge&logo=github" alt="Build" /></a>
  <a href="https://crates.io/crates/vrd"><img src="https://img.shields.io/crates/v/vrd.svg?style=for-the-badge&color=fc8d62&logo=rust" alt="Crates.io" /></a>
  <a href="https://docs.rs/vrd"><img src="https://img.shields.io/badge/docs.rs-vrd-66c2a5?style=for-the-badge&labelColor=555555&logo=docs.rs" alt="Docs.rs" /></a>
  <a href="https://lib.rs/crates/vrd"><img src="https://img.shields.io/badge/lib.rs-vrd-orange.svg?style=for-the-badge" alt="lib.rs" /></a>
  <a href="https://api.securityscorecards.dev/projects/github.com/sebastienrousseau/vrd"><img src="https://api.securityscorecards.dev/projects/github.com/sebastienrousseau/vrd/badge" alt="OpenSSF Scorecard" /></a>
</p>

---

## Contents

**Getting started**

- [Install](#install) - Cargo, MSRV, targets
- [Quick Start](#quick-start) - entropy-seeded, deterministic, `no_std`
- [Choosing a backend](#choosing-a-backend) - Xoshiro / MT / PCG / ChaCha20

**Library reference**

- [Cargo features](#cargo-features) - feature-flag matrix
- [Library Usage](#library-usage) - common idioms
- [Statistical validation](#statistical-validation) - PractRand harness
- [Quasi-random sequences](#quasi-random-sequences) - Halton, Sobol, VdC
- [Examples](#examples) - runnable example index
- [How vrd compares](#how-vrd-compares) - feature matrix vs `rand`, `fastrand`, `oorandom`

**Operational**

- [When not to use vrd](#when-not-to-use-vrd) - limitations
- [Migrating from `rand`](#migrating-from-rand) - name-for-name mapping
- [Migrating from earlier `0.0.x`](#migrating-from-earlier-00x) - breaking changes
- [Development](#development) - make targets, CI, performance knobs
- [FAQ](#faq) - common questions
- [Documentation](#documentation) - extended reading
- [License](#license)

---

## Install

```bash
cargo add vrd
```

Or in `Cargo.toml`:

```toml
[dependencies]
vrd = "0.0.12"
```

Requires [Rust](https://rustup.rs/) 1.70.0 or later. Builds for macOS, Linux, Windows, `no_std` embedded targets (Cortex-M, `thumbv7em-none-eabihf`), and `wasm32-unknown-unknown` - all validated in CI.

---

## Quick Start

```rust
use vrd::Random;

fn main() {
    let mut rng = Random::new();              // entropy-seeded Xoshiro256++

    println!("u32:        {}", rng.rand());
    println!("u64:        {}", rng.u64());
    println!("[1, 100]:   {}", rng.int(1, 100));
    println!("[0.0, 1.0): {}", rng.float());
}
```

### Deterministic sequences

```rust
use vrd::Random;

let mut rng = Random::from_u64_seed(0xCAFE_BABE);
let a = rng.rand();
let b = rng.rand();
// Re-seed with the same value to reproduce.
```

### `no_std` embedded usage

```rust
use vrd::Random;

// Allocation-free; works on any target - including Cortex-M.
let mut rng = Random::from_seed([0x42u8; 32]);
let n = rng.rand();
```

### Mersenne Twister (legacy reproducibility)

```rust
use vrd::Random;

let mut mt = Random::new_mersenne_twister();   // alloc + std
let v = mt.rand();
```

### Crypto-quality randomness

```rust
# #[cfg(all(feature = "crypto", feature = "std"))]
# {
use vrd::Random;

let mut rng = Random::new_secure();            // ChaCha20, OS-seeded
let token = rng.base64_token(32);
# let _ = token;
# }
```

---

## Choosing a backend

Default `Random` is non-cryptographic Xoshiro256++. For credentials, session IDs, or anything an attacker would benefit from predicting, enable the `crypto` feature and construct via [`Random::new_secure`] (entropy-seeded) or [`Random::from_secure_seed`] (deterministic).

| Backend | Constructor | State | Speed (u32) | Crypto-quality? |
| :--- | :--- | ---: | ---: | :---: |
| **Xoshiro256++** | `Random::new()` | 32 B | 3.1 ns | no |
| **Mersenne Twister** | `Random::new_mersenne_twister()` | 2 488 B | 3.1 ns | no |
| **PCG32 / PCG64** | `Random::new_pcg32()` / `new_pcg64()` | 16 / 32 B | **2.7 ns** / 4.3 ns | no |
| **ChaCha20** | `Random::new_secure()` | ~256 B | ~9 ns | **yes** |

Times measured through the `Random` facade on Apple Silicon. PCG32 is the fastest backend; ChaCha20 is the only crypto-quality one.

[`Random::new_secure`]: https://docs.rs/vrd/latest/vrd/struct.Random.html#method.new_secure
[`Random::from_secure_seed`]: https://docs.rs/vrd/latest/vrd/struct.Random.html#method.from_secure_seed

---

## Cargo features

| Flag | Default? | What it does |
| :--- | :--- | :--- |
| `std` | yes | Entropy seeding via `rand::rng()`; `std::error::Error` impls. |
| `alloc` | via `std` | `Random::bytes`, `Random::string`, `Random::sample`, `Random::uuid_v4`, `Random::hex_token`, `Random::base64_token`, the heap-stored Mersenne Twister backend. |
| `serde` | no | `Serialize` / `Deserialize` derives for the public types. |
| `simd` | no | SIMD-batched `fill_bytes` on AArch64 NEON / x86_64 AVX2 (~2–3× on bulk byte fills). |
| `pcg` | no | PCG32 (16 B state) and PCG64 (32 B state) as additional backends. |
| `crypto` | no | ChaCha20 CSPRNG backend: `Random::new_secure()`, `Random::from_secure_seed`. |
| `quasirandom` | no | Halton / Sobol / Van der Corput low-discrepancy sequences for Monte Carlo. |
| `crush` | no | Optional PractRand validation example. Informational only. |

Disable defaults to ship into `no_std`:

```toml
vrd = { version = "0.0.12", default-features = false }            # core only
vrd = { version = "0.0.12", default-features = false, features = ["alloc"] }  # core + alloc
```

---

## Library Usage

Every public method is documented at [docs.rs/vrd](https://docs.rs/vrd) with a worked example. The handful you'll reach for first:

```rust
use vrd::Random;

# #[cfg(feature = "std")]
# {
let mut rng = Random::new();                  // entropy-seeded Xoshiro256++

let n: u32 = rng.rand();                      // any u32
let n: u64 = rng.u64();                       // any u64
let n      = rng.int(1, 100);                 // i32 in [1, 100], uniform
let n      = rng.uint(1, 100);                // u32 in [1, 100], uniform
let f      = rng.double();                    // f64 in [0.0, 1.0)
let b      = rng.bool(0.5);                   // 50/50 coin
let pick   = rng.choose(&[10, 20, 30]);       // Option<&T>

#[cfg(feature = "alloc")]
let buf    = rng.bytes(32);                   // Vec<u8>, 32 random bytes
# }
```

**Non-security tokens** (correlation IDs, log markers, debug fixtures):

```rust
# #[cfg(all(feature = "alloc", feature = "std"))]
# {
use vrd::Random;
let mut rng = Random::new();

let trace_id = rng.uuid_v4_bytes();           // [u8; 16], no_std
let trace_id = rng.uuid_v4();                 // RFC 4122 hyphenated, alloc
let log_id   = rng.hex_token(16);             // 32 lowercase hex chars
let csrf_id  = rng.base64_token(15);          // 20 URL-safe base64 chars (no padding)
# }
```

For **security-sensitive** tokens (API keys, session IDs, password-reset links, CSRF tokens), enable the `crypto` feature and use `Random::new_secure()`.

**Save and restore state** (with the `serde` feature):

```toml
vrd = { version = "0.0.12", features = ["serde"] }
```

```rust,ignore
use vrd::Random;

let mut rng = Random::from_u64_seed(42);
let snap = serde_json::to_string(&rng).unwrap();

let mut restored: Random = serde_json::from_str(&snap).unwrap();
assert_eq!(rng.rand(), restored.rand());      // identical state, identical output
```

**User-defined distributions** (with the built-in `Distribution<T>` trait):

```rust
use vrd::{Distribution, Random};

struct Bernoulli { p: f64 }

impl Distribution<bool> for Bernoulli {
    fn sample(&self, rng: &mut Random) -> bool {
        rng.double() < self.p
    }
}

let mut rng = Random::from_u64_seed(1);
let coin = Bernoulli { p: 0.5 }.sample(&mut rng);
# let _ = coin;
```

---

## Statistical validation

Beyond Xoshiro256++ and MT19937's published academic pedigree, vrd ships a reproducible PractRand harness. See [BENCHMARKS.md](BENCHMARKS.md) for the latest pass-count table per backend; reproduce locally with `cargo run --release --example crush --features crush`. The example is informational - CI does **not** gate on it.

---

## Quasi-random sequences

Low-discrepancy sequences for Monte Carlo integration, ray-tracing, and high-dimensional optimisation. Variance scales `O((log n)^d / n)` rather than `O(1/√n)` for a uniform PRNG. Behind the `quasirandom` feature:

```toml
vrd = { version = "0.0.12", features = ["quasirandom"] }
```

```rust
use vrd::quasirandom::{HaltonSequence, SobolSequence, VanDerCorputSequence};

// 1-D Van der Corput in base 2: 0.5, 0.25, 0.75, 0.125, ...
let mut vdc = VanDerCorputSequence::new(2);
let _ = vdc.next_point();

// 2-D Halton across primes (2, 3); up to 32 dimensions shipped.
let mut h = HaltonSequence::new(2);
let _ = h.next_point::<2>();

// 6-D Sobol with Bratley-Fox direction numbers; up to 6 dimensions
// shipped (extending past that needs the Joe-Kuo D6 table).
let mut s = SobolSequence::new(6);
let _ = s.next_point::<6>();
```

Three constructions cover the standard ground; see [`examples/halton.rs`](examples/halton.rs) and [`examples/sobol.rs`](examples/sobol.rs) for Monte Carlo π convergence demos.

---

## Examples

44 runnable examples under [`examples/`](examples/). Browse the orchestrator with `cargo run --example all`. Highlights:

| Topic | Example | Run |
|---|---|---|
| Hello / basics | `hello`, `basics`, `seed` | `cargo run --example basics` |
| Bytes / floats / bools | `bytes`, `floats`, `bools`, `chars`, `strings` | `cargo run --example bytes` |
| Bounded sampling | `bounded`, `unbiased` | `cargo run --example bounded` |
| Distributions | `normal`, `exponential`, `poisson`, `distribution` | `cargo run --example normal` |
| Slice ops | `choose`, `shuffle`, `sample`, `slice`, `weighted` | `cargo run --example shuffle` |
| Backends | `xoshiro`, `mersenne`, `pcg`, `secure`, `backends` | `cargo run --example backends --features pcg,crypto` |
| Quasi-random | `halton`, `sobol` | `cargo run --example sobol --features quasirandom` |
| Parallel | `split` | `cargo run --example split` |
| Apps | `dice`, `lottery`, `passwords`, `monte` | `cargo run --example monte` |
| Helpers | `iterators`, `uuid`, `tokens` | `cargo run --example uuid` |
| Validation | `crush` | `cargo run --release --example crush --features crush` |

---

## How vrd compares

|  | `vrd` | `rand` 0.10 | `fastrand` 2.x | `oorandom` 11.x |
| :-- | :-: | :-: | :-: | :-: |
| Default backend | Xoshiro256++ | ChaCha12 / SmallRng | Wyrand | PCG family |
| MT19937 backend | ✓ (built-in) | external (`rand_mt`) | - | - |
| PCG32 / PCG64 | ✓ (`pcg` feature) | external (`pcg_rand`) | - | ✓ (default) |
| ChaCha20 CSPRNG | ✓ (`crypto` feature) | ✓ (`rand_chacha`) | - | - |
| Quasi-random (Halton / Sobol / VdC) | ✓ (`quasirandom` feature) | - | - | - |
| Pluggable `Distribution<T>` trait | ✓ | ✓ (via `rand_distr`) | - | - |
| SIMD-batched `fill_bytes` | ✓ (`simd` feature) | - | - | - |
| Pure `no_std` core | ✓ | partial | ✓ | ✓ |
| Cortex-M + WASM CI gated | ✓ | - | - | - |
| Unbiased bounded sampling (Lemire) | ✓ | ✓ | ✓ | - |
| Bit-precise floats (24-bit `f32` / 53-bit `f64`) | ✓ | ✓ | partial | ✓ |
| Built-in `uuid_v4` / `uuid_v4_bytes` | ✓ | needs `uuid` | - | - |
| Built-in `hex_token` / `base64_token` | ✓ | needs `hex` + `base64` | - | - |
| Output stability commitment | ✓ (patch) | explicitly **none** | - | - |
| `rand` 0.10 traits (`TryRng`, `SeedableRng`) | ✓ | (native) | - | - |

**Reach for `vrd`** when you want one small crate that covers fast non-cryptographic RNG, MT19937 for legacy reproducibility, PCG for scientific computing, ChaCha20 for crypto-quality tokens, and quasi-random sequences for Monte Carlo - across `std`, `no_std + alloc`, embedded (Cortex-M), and WebAssembly - without stitching together five other crates.

**Reach for `rand` + `rand_distr`** when you need the full statistical-distribution catalogue or are already deep in the rand-ecosystem trait stack.

### What you don't have to depend on

Pulling `vrd` in instead of `rand` + companion crates typically lets you drop these from your dependency tree:

- `uuid` - covered by `Random::uuid_v4` / `uuid_v4_bytes`
- `hex` or `data-encoding` - covered by `Random::hex_token`
- `base64` - covered by `Random::base64_token`
- `rand_distr` - if `uniform` / `normal` / `exponential` / `poisson` cover your needs
- `rand_chacha` - covered by the `crypto` feature
- `pcg_rand` - covered by the `pcg` feature

Fewer transitive crates, less compiled code, fewer audit boundaries to track.

---

## When not to use vrd

- **You need the rand-distr catalogue.** vrd ships four built-in distributions (`uniform`, `normal`, `exponential`, `poisson`) plus a pluggable `Distribution<T>` trait. If you need the full 20+ rand_distr set (binomial, gamma, log-normal, Cauchy, etc.) reach for `rand` + `rand_distr` until vrd ports the missing few.
- **You need fork-safe parallel deterministic streams beyond Xoshiro.** `Random::split()` works on the default backend; the MT / PCG / ChaCha20 backends return `None`. For massive Rayon-style fan-out across non-Xoshiro backends, derive distinct seeds per worker.
- **You're already deep in the rand-ecosystem trait stack.** vrd implements rand 0.10's `TryRng` / `Rng` / `SeedableRng`, so it interoperates - but if your codebase passes `rand::rngs::ThreadRng` everywhere, the migration cost may not be worth it.
- **You need cryptographic randomness without enabling a feature flag.** vrd's default backend is non-cryptographic. Enable the `crypto` feature and use `Random::new_secure()` for CSPRNG output.

---

## Migrating from `rand`

`vrd` implements the rand 0.10 traits, so most idioms translate directly:

| `rand 0.10` | `vrd` equivalent |
| :-- | :-- |
| `let mut rng = rand::rng();` | `let mut rng = Random::new();` |
| `rng.random::<u32>()` | `rng.rand()` |
| `rng.random_range(0..n)` | `rng.uint(0, n - 1)` |
| `rng.fill_bytes(&mut buf)` | `rng.try_fill_bytes(&mut buf).unwrap()` |
| `slice.choose(&mut rng)` | `rng.choose(slice)` |
| `slice.shuffle(&mut rng)` (`alloc`) | `rng.shuffle(slice)` (`alloc`) |
| `rand::rngs::StdRng::seed_from_u64(s)` | `Random::from_u64_seed(s)` |
| `rand_chacha::ChaCha20Rng::from_seed(s)` | `Random::from_secure_seed(s)` (`crypto`) |
| `rand_chacha::ChaCha20Rng::from_os_rng()` | `Random::new_secure()` (`crypto`) |

Or pass a `Random` directly to any crate that takes a `rand_core::TryRng`, `Rng`, or `SeedableRng` - vrd implements all three.

---

## Migrating from earlier `0.0.x`

The 0.0.10 release modernised the architecture. Breaking changes carried into 0.0.11:

- `Random` defaults to **Xoshiro256++**, not Mersenne Twister. Use `Random::new_mersenne_twister()` if you need MT.
- The generic `fill()` method is gone - use `Random::try_fill_bytes(&mut [u8])` from the `rand_core::TryRng` trait, or build types from `rand()` / `u64()`.
- `int`, `uint`, `random_range` are **unbiased** (Lemire's nearly-divisionless method). Outputs differ from pre-0.0.10 versions for the same seed.
- `MersenneTwisterError` lost its `IoError` and `SerializationError` variants - direct `serde_json` / `serde_yml` / `toml` helpers were removed. Use `serde` directly with the `serde` feature for that.
- `VrdError::GeneralError` carries `&'static str` instead of `String` - `no_std`-friendly.
- The `logging` feature and `create_log_entry` helper are gone - vrd is no longer a log-formatting library.

0.0.11-specific:

- `Random` and `RngBackend` no longer derive `Eq` / `Hash` / `Ord` / `PartialOrd`. `PartialEq` is kept for snapshot / determinism comparisons. The drop is needed because `ChaCha20Rng` (the new `crypto` backend's underlying type) doesn't implement them.
- `#![forbid(unsafe_code)]` becomes `#![deny(unsafe_code)]` at the crate root so the optional `xoshiro_simd` module can lift the deny locally for architecture intrinsics. All other modules remain `unsafe`-free.

See [CHANGELOG.md](CHANGELOG.md) and [RELEASE-NOTES-v0.0.11.md](RELEASE-NOTES-v0.0.11.md) for the full diff.

---

## Development

```bash
make                                                                 # check + clippy + test (default)
make test                                                            # cargo test --all-features
make bench                                                           # cargo bench
make doc                                                             # cargo doc --no-deps --all-features
make examples                                                        # run every example with [ok]/[fail]
make crush                                                           # PractRand harness (needs RNG_test in PATH)
make fuzz-quick                                                      # 60 s per fuzz target (cargo-fuzz)
cargo check --target thumbv7em-none-eabihf --no-default-features     # Cortex-M smoke check
cargo check --target wasm32-unknown-unknown --no-default-features    # WebAssembly smoke check
```

### Squeezing more performance

The default release profile (`opt-level = 3`, `lto = true`, `codegen-units = 1`) gets vrd to ~1.1 ns per `u32` on Apple Silicon. Three knobs are available to downstream consumers who want every cycle:

**Native CPU targeting** - enables AArch64 NEON or x86 AVX/AVX-512 codegen for whichever host you're running on:

```toml
# .cargo/config.toml in your binary crate
[build]
rustflags = ["-C", "target-cpu=native"]
```

`target-cpu=native` is **not** baked into vrd's release profile because it would break `cargo install` for users on machines that download crates as binaries. Set it in the consuming crate.

**`simd` feature for bulk byte generation** - opts into SIMD-batched `fill_bytes` that holds K independent Xoshiro256++ states in vector registers (K = 2 on AArch64 NEON, K = 4 on x86_64 AVX2). ~2.2× faster on 1 KiB and ~3× on 16 KiB:

```toml
vrd = { version = "0.0.12", features = ["simd"] }
```

The SIMD path produces a **different byte stream** than the scalar path for the same seed - see [`xoshiro_simd`](https://docs.rs/vrd/latest/vrd/xoshiro_simd/) for the contract. Reproducibility-sensitive code must stay on the scalar default.

**Profile-Guided Optimization (PGO)** - typically yields 5–15% on hot loops:

```bash
# 1. Instrumented build that emits .profraw counters
RUSTFLAGS="-Cprofile-generate=/tmp/pgo" cargo build --release
# 2. Run a representative workload to populate the profile
./target/release/your-app
# 3. Merge into a single .profdata
$(rustc --print sysroot)/lib/rustlib/*/bin/llvm-profdata merge -o /tmp/pgo/merged.profdata /tmp/pgo
# 4. Rebuild with the profile applied
RUSTFLAGS="-Cprofile-use=/tmp/pgo/merged.profdata" cargo build --release
```

See [CONTRIBUTING.md](CONTRIBUTING.md) for setup, signed commits, and PR guidelines.

---

## FAQ

### Do I need one RNG per thread?

Yes. `Random` (and `Xoshiro256PlusPlus`, and `MersenneTwister`) hold mutable state and are not designed for concurrent access. The standard pattern is one RNG per thread, seeded distinctly:

```rust
use vrd::Random;

# let thread_id: u64 = 0;
let mut rng = Random::from_u64_seed(thread_id);   // distinct per thread
let _ = rng.rand();
```

For parallel deterministic streams that don't drift, use `Random::split()` on the default Xoshiro backend - returns `Some(Random)` with a stream 2¹²⁸ calls ahead.

### Does vrd work without `std`?

Yes. With `default-features = false`, vrd compiles for pure `no_std` targets - Cortex-M is gated in CI on every PR. The `alloc` feature unlocks `Vec` / `String` / `Box`-backed APIs (`bytes`, `string`, `sample`, `shuffle`, `uuid_v4`, `hex_token`, `base64_token`, the Mersenne Twister backend). Without `alloc`, `Random::from_seed([u8; 32])` and `Random::from_u64_seed(u64)` give you a fully-functional Xoshiro256++ on bare metal.

### Does vrd work in WebAssembly?

Yes. `wasm32-unknown-unknown` is gated in CI under both `--no-default-features` and `--features alloc`. Default WebAssembly has no entropy source, so seed manually with `Random::from_seed([u8; 32])` or `Random::from_u64_seed(u64)` rather than `Random::new()`. If you want OS-level entropy in the browser, enable `getrandom`'s `js` feature in your binary crate - that's downstream's choice, not vrd's.

### Why ship Mersenne Twister at all if Xoshiro is the default?

Reproducibility against existing MT-generated test vectors. Numerical-simulation pipelines, scientific software, and tooling that emits "random-looking" reference data often pin MT19937 because that's what NumPy / older `rand` / SciPy / MATLAB historically used. Reach for `Random::new_mersenne_twister()` (or `new_mersenne_twister_with_seed(u32)` for `alloc`-only) only when you need bit-for-bit MT19937 output.

### Can I get the same sequence on two machines?

Yes - use `Random::from_seed([u8; 32])` or `Random::from_u64_seed(u64)`. Both are deterministic and allocation-free. The output is byte-identical across architectures (x86, ARM, RISC-V, WebAssembly) - only floating-point operations *downstream* of the RNG (your code's arithmetic) may differ across targets.

### Is the output stable across vrd versions?

For a given seed and method, vrd commits to bit-stable output across **patch** releases. Algorithm changes (e.g., the v0.0.11 Ziggurat `normal()`) bump at least the minor version and are flagged in the `CHANGELOG`'s `Migration` section, naming the affected methods. Once vrd reaches 1.0, this stability commitment will extend to minor releases as well. The `rand` crate explicitly does not guarantee either. If you have golden-file tests, fuzzing corpora, or reproducible-research workflows depending on a stable RNG sequence, that's a meaningful difference.

### How big is the RNG state?

- `Xoshiro256PlusPlus`: **32 bytes** (four `u64` words). Stored inline.
- `Pcg32`: **16 bytes** (state + increment).
- `Pcg64`: **32 bytes** (state + increment).
- `MersenneTwister`: **~2.5 KB** (624 × `u32` + index). Heap-stored when wrapped in `Random` to keep the enum discriminant small.
- `ChaCha20Rng` (via `crypto` feature): **~256 bytes**. Heap-stored in `Random`.
- `Random`: a tagged enum sized for the largest inline variant. The wrapper-vs-direct dispatch overhead is **zero** - the inliner elides the match completely (verified in `cargo bench`).

### How fast is it?

`cargo bench` runs head-to-head against `fastrand` 2.x and `rand::rng()` on `u32`, `u64`, byte fills, bounded sampling, and distribution sampling. On Apple Silicon, Xoshiro vrd produces a `u32` in ~3.1 ns through the `Random` facade (~1.1 ns inline); PCG32 is ~2.7 ns; the Ziggurat `normal()` is ~3.7 ns. Run them locally - absolute numbers are workload- and platform-dependent.

---

## Documentation

- [`README.md`](README.md) - this file.
- [`CHANGELOG.md`](CHANGELOG.md) - per-version diff.
- [`BENCHMARKS.md`](BENCHMARKS.md) - full benchmark tables.
- [`RELEASE-NOTES-v0.0.12.md`](RELEASE-NOTES-v0.0.12.md) - current release notes.
- [`RELEASE-NOTES-v0.0.11.md`](RELEASE-NOTES-v0.0.11.md) - prior release.
- [`CONTRIBUTING.md`](CONTRIBUTING.md) - pre-submit checklist and PractRand install pointers.
- [`docs.rs/vrd`](https://docs.rs/vrd) - full API documentation, every public method worked.

---

## License

Dual-licensed under [Apache 2.0](https://www.apache.org/licenses/LICENSE-2.0) or [MIT](https://opensource.org/licenses/MIT), at your option.

<p align="right"><a href="#vrd">Back to Top</a></p>