Skip to main content

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//! [![Crates.io](https://img.shields.io/crates/v/vrd.svg)](https://crates.io/crates/vrd)
27//! [![Docs.rs](https://img.shields.io/docsrs/vrd.svg)](https://docs.rs/vrd)
28//! [![License](https://img.shields.io/badge/license-MIT_or_Apache--2.0-blue.svg)](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}