vrd/lib.rs
1// Copyright © 2023-2026 vrd. All rights reserved.
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3// See LICENSE-APACHE.md and LICENSE-MIT.md in the repository root for full
4// license information.
5
6#![no_std]
7#![doc(
8 html_favicon_url = "https://cloudcdn.pro/vrd/v1/favicon.ico",
9 html_logo_url = "https://cloudcdn.pro/vrd/v1/logos/vrd.svg",
10 html_root_url = "https://docs.rs/vrd"
11)]
12#![crate_name = "vrd"]
13#![crate_type = "lib"]
14#![deny(missing_docs)]
15// Lib-scoped - examples / tests / benches stay unaffected.
16#![warn(clippy::missing_docs_in_private_items)]
17#![warn(rust_2018_idioms)]
18// `deny`, not `forbid`, so the optional `simd` module (which needs
19// architecture intrinsics) can lift it with a module-local `#[allow]`.
20// All other modules must remain free of `unsafe`.
21#![deny(unsafe_code)]
22#![doc = "Minimum supported Rust version: 1.70.0"]
23
24//! # Versatile Random Distributions (VRD)
25//!
26//! [](https://crates.io/crates/vrd)
27//! [](https://docs.rs/vrd)
28//! [](https://github.com/sebastienrousseau/vrd#license)
29//!
30//! A lightweight, `no_std`-friendly random number generator backed by
31//! **Xoshiro256++**, with optional **Mersenne Twister (MT19937)** support.
32//!
33//! ## Features
34//! - **High performance:** Xoshiro256++ default - 32-byte state, period
35//! 2^256 - 1, SplitMix64 seed whitening.
36//! - **Legacy reproducibility:** opt-in MT19937 backend.
37//! `Random::new_mersenne_twister()` requires `alloc + std`;
38//! `Random::new_mersenne_twister_with_seed(u32)` is `alloc`-only.
39//! - **`no_std` ready:** pure-core build with `default-features = false`,
40//! validated on `thumbv7em-none-eabihf` (Cortex-M) and
41//! `wasm32-unknown-unknown` (WebAssembly) in CI.
42//! - **Unbiased sampling:** `int`, `uint`, `random_range`, and the public
43//! `bounded` use Lemire's nearly-divisionless method.
44//! - **Bit-precise floats:** `float()` carries 24 mantissa bits, `double()`
45//! carries 53. Always `[0.0, 1.0)`.
46//! - **Distributions:** `uniform(low, high)`, `normal`, `exponential`,
47//! `poisson` - `std`-free via `libm`.
48//! - **Convenience helpers:** `iter_u32` / `iter_u64` / `iter_bytes`
49//! iterator adapters; `uuid_v4_bytes` (`no_std`) and `uuid_v4`
50//! (`alloc`); `hex_token` and `base64_token` for URL-safe random
51//! tokens.
52//! - **`rand 0.10` traits:** `TryRng`, the blanket-implemented `Rng`, and
53//! `SeedableRng`.
54//!
55//! ## Quickstart
56//!
57//! ```
58//! use vrd::Random;
59//!
60//! let mut rng = Random::from_u64_seed(42); // deterministic, allocation-free
61//!
62//! let n: u32 = rng.rand(); // any u32
63//! let _ = rng.u64(); // any u64
64//! let _ = rng.int(1, 100); // i32 in [1, 100], uniform
65//! let _ = rng.double(); // f64 in [0.0, 1.0)
66//! let _ = rng.bool(0.5); // 50/50 coin
67//! assert!(n > 0 || n == 0);
68//! ```
69//!
70//! Use [`Random::new()`] for entropy-seeded randomness on `std` targets,
71//! [`Random::from_seed()`] / [`Random::from_u64_seed()`] for deterministic
72//! / `no_std` use, and [`Random::new_mersenne_twister()`] / [`Random::new_mersenne_twister_with_seed()`]
73//! when you need bit-for-bit MT19937 reproducibility against existing
74//! test vectors.
75//!
76//! ## Choosing a backend
77//!
78//! Default `Random` is non-cryptographic Xoshiro256++. For
79//! credentials, session IDs, or anything an attacker would benefit
80//! from predicting, enable the `crypto` feature and construct via
81//! [`Random::new_secure`] (entropy-seeded) or
82//! [`Random::from_secure_seed`] (deterministic). The other backends
83//! cover different speed / state-size / reproducibility points:
84//!
85//! | Backend | Constructor | State | Crypto-quality? |
86//! | :--- | :--- | ---: | :---: |
87//! | Xoshiro256++ | [`Random::new`] | 32 B | no |
88//! | MT19937 | [`Random::new_mersenne_twister`] | 2 488 B | no |
89//! | PCG32 / PCG64 | [`Random::new_pcg32`] / [`Random::new_pcg64`] | 16 / 32 B | no |
90//! | ChaCha20 | [`Random::new_secure`] | ~256 B | **yes** |
91//!
92//! ## Optional features
93//!
94//! - `simd` - SIMD-batched `fill_bytes` (~2–3× bulk throughput).
95//! - `pcg` - PCG32 / PCG64 backends.
96//! - `crypto` - ChaCha20 CSPRNG backend.
97//! - `quasirandom` - Halton / Sobol / Van der Corput low-discrepancy
98//! sequences for Monte Carlo integration.
99//! - `serde` - `Serialize` / `Deserialize` on the public types.
100
101#[cfg(feature = "alloc")]
102extern crate alloc;
103
104#[cfg(feature = "std")]
105extern crate std;
106
107use core::fmt;
108
109/// Crate-level error type for the `vrd` library.
110///
111/// This error type is used to represent general failures within the library.
112/// It is kept allocation-free by using static error messages, ensuring it
113/// works correctly in pure `no_std` environments without requiring an
114/// allocator.
115///
116/// # Examples
117///
118/// ```
119/// use vrd::VrdError;
120///
121/// let err = VrdError::GeneralError("something went wrong");
122/// println!("{}", err);
123/// ```
124#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
125#[non_exhaustive]
126pub enum VrdError {
127 /// A general error with a static message.
128 ///
129 /// This variant is used for unexpected conditions where a more specific
130 /// error type isn't applicable.
131 GeneralError(&'static str),
132}
133
134impl fmt::Display for VrdError {
135 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
136 match self {
137 VrdError::GeneralError(msg) => {
138 write!(f, "General error: {}", msg)
139 }
140 }
141 }
142}
143
144#[cfg(feature = "std")]
145impl std::error::Error for VrdError {}
146
147/// ChaCha20 CSPRNG (feature `crypto`).
148#[cfg(feature = "crypto")]
149pub mod chacha;
150/// Pluggable `Distribution` trait and built-in samplers.
151pub mod distribution;
152/// Convenience macros.
153pub mod macros;
154/// Mersenne Twister configuration and constants.
155pub mod mersenne_twister;
156/// PCG32 / PCG64 generators (feature `pcg`).
157#[cfg(feature = "pcg")]
158pub mod pcg;
159/// Quasi-random low-discrepancy sequences (feature `quasirandom`).
160#[cfg(feature = "quasirandom")]
161pub mod quasirandom;
162/// The core `Random` facade.
163pub mod random;
164/// Xoshiro256++ implementation.
165pub mod xoshiro;
166/// SIMD-batched `fill_bytes` (feature `simd`).
167///
168/// Architecture-conditional (NEON on aarch64, AVX2 on x86_64);
169/// excluded from coverage measurement via `.tarpaulin.toml` because
170/// a single-platform tarpaulin run can never observe both halves.
171/// Validated by the dedicated `simd` CI matrix job that runs
172/// `cargo test --features simd` on both ubuntu-latest and
173/// macos-latest.
174#[cfg(feature = "simd")]
175pub mod xoshiro_simd;
176/// Ziggurat sampler for `Random::normal()`.
177mod ziggurat;
178
179pub use distribution::Distribution;
180pub use mersenne_twister::{
181 MersenneTwisterConfig, MersenneTwisterError, MersenneTwisterParams,
182};
183pub use random::{FloatExt, Random, RngBackend};
184
185#[cfg(test)]
186mod tests {
187 use super::*;
188
189 #[cfg(feature = "alloc")]
190 use alloc::format;
191 #[cfg(all(not(feature = "alloc"), feature = "std"))]
192 use std::format;
193
194 #[test]
195 #[cfg(any(feature = "alloc", feature = "std"))]
196 fn test_vrd_error_display() {
197 let err = VrdError::GeneralError("test error");
198 let s = format!("{}", err);
199 assert_eq!(s, "General error: test error");
200 }
201
202 #[test]
203 #[cfg(any(feature = "alloc", feature = "std"))]
204 fn test_vrd_error_debug() {
205 let err = VrdError::GeneralError("test error");
206 let s = format!("{:?}", err);
207 assert!(s.contains("GeneralError"));
208 assert!(s.contains("test error"));
209 }
210}