diff --git a/CHANGELOG.md b/CHANGELOG.md index 5d78a8d8ca..71c7353174 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,14 +21,11 @@ You may also find the [Upgrade Guide](https://rust-random.github.io/book/update. - Rename `rand::distributions` to `rand::distr` (#1470) - The `serde1` feature has been renamed `serde` (#1477) - The implicit feature `rand_chacha` has been removed. This is enabled by `std_rng`. (#1473) -- Mark `WeightError`, `PoissonError`, `BinomialError` as `#[non_exhaustive]` (#1480). +- Mark `WeightError` as `#[non_exhaustive]` (#1480). - Add `p()` for `Bernoulli` to access probability (#1481) - Add `UniformUsize` and use to make `Uniform` for `usize` portable (#1487) -- Remove support for generating `isize` and `usize` values with `Standard`, `Uniform` and `Fill` and usage as a `WeightedAliasIndex` weight (#1487) - Require `Clone` and `AsRef` bound for `SeedableRng::Seed`. (#1491) - Improve SmallRng initialization performance (#1482) -- Implement `Distribution` for `Poisson` (#1498) -- Limit the maximal acceptable lambda for `Poisson` to solve (#1312) (#1498) - Rename `Rng::gen_iter` to `random_iter` (#1500) - Rename `rand::thread_rng()` to `rand::rng()`, and remove from the prelude (#1506) - Remove `rand::random()` from the prelude (#1506) diff --git a/README.md b/README.md index 1dbe4c55f1..58f363896b 100644 --- a/README.md +++ b/README.md @@ -6,20 +6,20 @@ [![API](https://img.shields.io/badge/api-master-yellow.svg)](https://rust-random.github.io/rand/rand) [![API](https://docs.rs/rand/badge.svg)](https://docs.rs/rand) -Rand is a Rust library supporting random generators: +Rand is a set of crates supporting (pseudo-)random generators: -- A standard RNG trait: [`rand_core::RngCore`](https://docs.rs/rand_core/latest/rand_core/trait.RngCore.html) -- Fast implementations of the best-in-class [cryptographic](https://rust-random.github.io/book/guide-rngs.html#cryptographically-secure-pseudo-random-number-generators-csprngs) and - [non-cryptographic](https://rust-random.github.io/book/guide-rngs.html#basic-pseudo-random-number-generators-prngs) generators: [`rand::rngs`](https://docs.rs/rand/latest/rand/rngs/index.html), and more RNGs: [`rand_chacha`](https://docs.rs/rand_chacha), [`rand_xoshiro`](https://docs.rs/rand_xoshiro/), [`rand_pcg`](https://docs.rs/rand_pcg/), [rngs repo](https://github.com/rust-random/rngs/) -- [`rand::rng`](https://docs.rs/rand/latest/rand/fn.rng.html) is an asymtotically-fast, reasonably secure generator available on all `std` targets -- Secure seeding via the [`getrandom` crate](https://crates.io/crates/getrandom) +- Built over a standard RNG trait: [`rand_core::RngCore`](https://docs.rs/rand_core/latest/rand_core/trait.RngCore.html) +- With fast implementations of both [strong](https://rust-random.github.io/book/guide-rngs.html#cryptographically-secure-pseudo-random-number-generators-csprngs) and + [small](https://rust-random.github.io/book/guide-rngs.html#basic-pseudo-random-number-generators-prngs) generators: [`rand::rngs`](https://docs.rs/rand/latest/rand/rngs/index.html), and more RNGs: [`rand_chacha`](https://docs.rs/rand_chacha), [`rand_xoshiro`](https://docs.rs/rand_xoshiro/), [`rand_pcg`](https://docs.rs/rand_pcg/), [rngs repo](https://github.com/rust-random/rngs/) +- [`rand::rng`](https://docs.rs/rand/latest/rand/fn.rng.html) is an asymptotically-fast, automatically-seeded and reasonably strong generator available on all `std` targets +- Direct support for seeding generators from the [`getrandom` crate](https://crates.io/crates/getrandom) -Supporting random value generation and random processes: +With broad support for random value generation and random processes: -- [`Standard`](https://docs.rs/rand/latest/rand/distributions/struct.Standard.html) random value generation -- Ranged [`Uniform`](https://docs.rs/rand/latest/rand/distributions/struct.Uniform.html) number generation for many types -- A flexible [`distributions`](https://docs.rs/rand/*/rand/distr/index.html) module -- Samplers for a large number of random number distributions via our own +- [`Standard`](https://docs.rs/rand/latest/rand/distributions/struct.Standard.html) random value sampling, + [`Uniform`](https://docs.rs/rand/latest/rand/distributions/struct.Uniform.html)-ranged value sampling + and [more](https://docs.rs/rand/latest/rand/distr/index.html) +- Samplers for a large number of non-uniform random number distributions via our own [`rand_distr`](https://docs.rs/rand_distr) and via the [`statrs`](https://docs.rs/statrs/0.13.0/statrs/) - Random processes (mostly choose and shuffle) via [`rand::seq`](https://docs.rs/rand/latest/rand/seq/index.html) traits @@ -28,19 +28,23 @@ All with: - [Portably reproducible output](https://rust-random.github.io/book/portability.html) - `#[no_std]` compatibility (partial) -- *Many* performance optimisations +- *Many* performance optimisations thanks to contributions from the wide + user-base -It's also worth pointing out what Rand *is not*: +Rand **is not**: -- Small. Most low-level crates are small, but the higher-level `rand` and - `rand_distr` each contain a lot of functionality. +- Small (LOC). Most low-level crates are small, but the higher-level `rand` + and `rand_distr` each contain a lot of functionality. - Simple (implementation). We have a strong focus on correctness, speed and flexibility, but not simplicity. If you prefer a small-and-simple library, there are alternatives including [fastrand](https://crates.io/crates/fastrand) and [oorandom](https://crates.io/crates/oorandom). -- Slow. We take performance seriously, with considerations also for set-up - time of new distributions, commonly-used parameters, and parameters of the - current sampler. +- A cryptography library. Rand provides functionality for generating + unpredictable random data (potentially applicable depending on requirements) + but does not provide high-level cryptography functionality. + +Rand is a community project and cannot provide legally-binding guarantees of +security. Documentation: diff --git a/SECURITY.md b/SECURITY.md index 356fbe879d..26cf7c12fc 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -1,31 +1,46 @@ # Security Policy -## No guarantees +## Disclaimer -Support is provided on a best-effort bases only. -No binding guarantees can be provided. +Rand is a community project and cannot provide legally-binding guarantees of +security. ## Security premises -Rand provides the trait `rand_core::CryptoRng` aka `rand::CryptoRng` as a marker -trait. Generators implementing `RngCore` *and* `CryptoRng`, and given the -additional constraints that: +### Marker traits + +Rand provides the marker traits `CryptoRng`, `TryCryptoRng` and +`CryptoBlockRng`. Generators implementing one of these traits and used in a way +which meets the following additional constraints: - Instances of seedable RNGs (those implementing `SeedableRng`) are constructed with cryptographically secure seed values -- The state (memory) of the RNG and its seed value are not be exposed +- The state (memory) of the RNG and its seed value are not exposed are expected to provide the following: -- An attacker can gain no advantage over chance (50% for each bit) in - predicting the RNG output, even with full knowledge of all prior outputs. +- An attacker cannot predict the output with more accuracy than what would be + expected through pure chance since each possible output value of any method + under the above traits which generates output bytes (including + `RngCore::next_u32`, `RngCore::next_u64`, `RngCore::fill_bytes`, + `TryRngCore::try_next_u32`, `TryRngCore::try_next_u64`, + `TryRngCore::try_fill_bytes` and `BlockRngCore::generate`) should be equally + likely +- Knowledge of prior outputs from the generator does not aid an attacker in + predicting future outputs + +### Specific generators + +`OsRng` is a stateless "generator" implemented via [getrandom]. As such, it has +no possible state to leak and cannot be improperly seeded. + +`ThreadRng` will periodically reseed itself, thus placing an upper bound on the +number of bits of output from an instance before any advantage an attacker may +have gained through state-compromising side-channel attacks is lost. -For some RNGs, notably `OsRng`, `ThreadRng` and those wrapped by `ReseedingRng`, -we provide limited mitigations against side-channel attacks: +[getrandom]: https://crates.io/crates/getrandom -- After the state (memory) of an RNG is leaked, there is an upper-bound on the - number of bits of output by the RNG before prediction of output by an - observer again becomes computationally-infeasible +### Distributions Additionally, derivations from such an RNG (including the `Rng` trait, implementations of the `Distribution` trait, and `seq` algorithms) should not diff --git a/benches/benches/distr.rs b/benches/benches/distr.rs index 63d6a1034e..ec43e7e61c 100644 --- a/benches/benches/distr.rs +++ b/benches/benches/distr.rs @@ -159,7 +159,7 @@ fn bench(c: &mut Criterion) { g.finish(); let mut g = c.benchmark_group("zipf"); - distr_float!(g, "zipf", f64, Zipf::new(10, 1.5).unwrap()); + distr_float!(g, "zipf", f64, Zipf::new(10.0, 1.5).unwrap()); distr_float!(g, "zeta", f64, Zeta::new(1.5).unwrap()); g.finish(); diff --git a/rand_core/src/os.rs b/rand_core/src/os.rs index 86ae462e5a..78b689bc02 100644 --- a/rand_core/src/os.rs +++ b/rand_core/src/os.rs @@ -11,10 +11,9 @@ use crate::{TryCryptoRng, TryRngCore}; use getrandom::getrandom; -/// A random number generator that retrieves randomness from the -/// operating system. +/// An interface over the operating-system's random data source /// -/// This is a zero-sized struct. It can be freely constructed with `OsRng`. +/// This is a zero-sized struct. It can be freely constructed with just `OsRng`. /// /// The implementation is provided by the [getrandom] crate. Refer to /// [getrandom] documentation for details. @@ -32,7 +31,8 @@ use getrandom::getrandom; /// /// After the first successful call, it is highly unlikely that failures or /// significant delays will occur (although performance should be expected to -/// be much slower than a user-space PRNG). +/// be much slower than a user-space +/// [PRNG](https://rust-random.github.io/book/guide-gen.html#pseudo-random-number-generators)). /// /// # Usage example /// ``` diff --git a/rand_distr/CHANGELOG.md b/rand_distr/CHANGELOG.md index 87e2f68f1f..81b62a1f28 100644 --- a/rand_distr/CHANGELOG.md +++ b/rand_distr/CHANGELOG.md @@ -10,7 +10,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Move some of the computations in Binomial from `sample` to `new` (#1484) - Add Kolmogorov Smirnov test for sampling of `Normal` and `Binomial` (#1494) - Add Kolmogorov Smirnov test for more distributions (#1504) +- Mark `WeightError`, `PoissonError`, `BinomialError` as `#[non_exhaustive]` (#1480). +- Remove support for generating `isize` and `usize` values with `Standard`, `Uniform` and `Fill` and usage as a `WeightedAliasIndex` weight (#1487) +- Limit the maximal acceptable lambda for `Poisson` to solve (#1312) (#1498) - Fix bug in `Hypergeometric`, this is a Value-breaking change (#1510) +- Change parameter type of `Zipf::new`: `n` is now floating-point (#1518) ### Added - Add plots for `rand_distr` distributions to documentation (#1434) diff --git a/rand_distr/src/poisson.rs b/rand_distr/src/poisson.rs index f06c29aef4..20b41ace64 100644 --- a/rand_distr/src/poisson.rs +++ b/rand_distr/src/poisson.rs @@ -39,6 +39,17 @@ use rand::Rng; /// let v: f64 = poi.sample(&mut rand::rng()); /// println!("{} is from a Poisson(2) distribution", v); /// ``` +/// +/// # Integer vs FP return type +/// +/// This implementation uses floating-point (FP) logic internally. +/// +/// Due to the parameter limit λ < [Self::MAX_LAMBDA], it +/// statistically impossible to sample a value larger [`u64::MAX`]. As such, it +/// is reasonable to cast generated samples to `u64` using `as`: +/// `distr.sample(&mut rng) as u64` (and memory safe since Rust 1.45). +/// Similarly, when `λ < 4.2e9` it can be safely assumed that samples are less +/// than `u32::MAX`. #[derive(Clone, Copy, Debug, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct Poisson(Method) @@ -238,14 +249,6 @@ where } } -impl Distribution for Poisson { - #[inline] - fn sample(&self, rng: &mut R) -> u64 { - // `as` from float to int saturates - as Distribution>::sample(self, rng) as u64 - } -} - #[cfg(test)] mod test { use super::*; diff --git a/rand_distr/src/zeta.rs b/rand_distr/src/zeta.rs index 3c2f55546e..e06208c94e 100644 --- a/rand_distr/src/zeta.rs +++ b/rand_distr/src/zeta.rs @@ -40,6 +40,17 @@ use rand::{distr::OpenClosed01, Rng}; /// println!("{}", val); /// ``` /// +/// # Integer vs FP return type +/// +/// This implementation uses floating-point (FP) logic internally, which can +/// potentially generate very large samples (exceeding e.g. `u64::MAX`). +/// +/// It is *safe* to cast such results to an integer type using `as` +/// (e.g. `distr.sample(&mut rng) as u64`), since such casts are saturating +/// (e.g. `2f64.powi(64) as u64 == u64::MAX`). It is up to the user to +/// determine whether this potential loss of accuracy is acceptable +/// (this determination may depend on the distribution's parameters). +/// /// # Notes /// /// The zeta distribution has no upper limit. Sampled values may be infinite. diff --git a/rand_distr/src/zipf.rs b/rand_distr/src/zipf.rs index 0a56fdf818..45a8bf2b5c 100644 --- a/rand_distr/src/zipf.rs +++ b/rand_distr/src/zipf.rs @@ -35,10 +35,17 @@ use rand::Rng; /// use rand::prelude::*; /// use rand_distr::Zipf; /// -/// let val: f64 = rand::rng().sample(Zipf::new(10, 1.5).unwrap()); +/// let val: f64 = rand::rng().sample(Zipf::new(10.0, 1.5).unwrap()); /// println!("{}", val); /// ``` /// +/// # Integer vs FP return type +/// +/// This implementation uses floating-point (FP) logic internally. It may be +/// expected that the samples are no greater than `n`, thus it is reasonable to +/// cast generated samples to any integer type which can also represent `n` +/// (e.g. `distr.sample(&mut rng) as u64`). +/// /// # Implementation details /// /// Implemented via [rejection sampling](https://en.wikipedia.org/wiki/Rejection_sampling), @@ -85,16 +92,17 @@ where /// Construct a new `Zipf` distribution for a set with `n` elements and a /// frequency rank exponent `s`. /// - /// For large `n`, rounding may occur to fit the number into the float type. + /// The parameter `n` is typically integral, however we use type + ///
F: [Float]
in order to permit very large values + /// and since our implementation requires a floating-point type. #[inline] - pub fn new(n: u64, s: F) -> Result, Error> { + pub fn new(n: F, s: F) -> Result, Error> { if !(s >= F::zero()) { return Err(Error::STooSmall); } - if n < 1 { + if n < F::one() { return Err(Error::NTooSmall); } - let n = F::from(n).unwrap(); // This does not fail. let q = if s != F::one() { // Make sure to calculate the division only once. F::one() / (F::one() - s) @@ -166,24 +174,24 @@ mod tests { #[test] #[should_panic] fn zipf_s_too_small() { - Zipf::new(10, -1.).unwrap(); + Zipf::new(10., -1.).unwrap(); } #[test] #[should_panic] fn zipf_n_too_small() { - Zipf::new(0, 1.).unwrap(); + Zipf::new(0., 1.).unwrap(); } #[test] #[should_panic] fn zipf_nan() { - Zipf::new(10, f64::NAN).unwrap(); + Zipf::new(10., f64::NAN).unwrap(); } #[test] fn zipf_sample() { - let d = Zipf::new(10, 0.5).unwrap(); + let d = Zipf::new(10., 0.5).unwrap(); let mut rng = crate::test::rng(2); for _ in 0..1000 { let r = d.sample(&mut rng); @@ -193,7 +201,7 @@ mod tests { #[test] fn zipf_sample_s_1() { - let d = Zipf::new(10, 1.).unwrap(); + let d = Zipf::new(10., 1.).unwrap(); let mut rng = crate::test::rng(2); for _ in 0..1000 { let r = d.sample(&mut rng); @@ -203,7 +211,7 @@ mod tests { #[test] fn zipf_sample_s_0() { - let d = Zipf::new(10, 0.).unwrap(); + let d = Zipf::new(10., 0.).unwrap(); let mut rng = crate::test::rng(2); for _ in 0..1000 { let r = d.sample(&mut rng); @@ -214,7 +222,7 @@ mod tests { #[test] fn zipf_sample_large_n() { - let d = Zipf::new(u64::MAX, 1.5).unwrap(); + let d = Zipf::new(f64::MAX, 1.5).unwrap(); let mut rng = crate::test::rng(2); for _ in 0..1000 { let r = d.sample(&mut rng); @@ -225,12 +233,12 @@ mod tests { #[test] fn zipf_value_stability() { - test_samples(Zipf::new(10, 0.5).unwrap(), 0f32, &[10.0, 2.0, 6.0, 7.0]); - test_samples(Zipf::new(10, 2.0).unwrap(), 0f64, &[1.0, 2.0, 3.0, 2.0]); + test_samples(Zipf::new(10., 0.5).unwrap(), 0f32, &[10.0, 2.0, 6.0, 7.0]); + test_samples(Zipf::new(10., 2.0).unwrap(), 0f64, &[1.0, 2.0, 3.0, 2.0]); } #[test] fn zipf_distributions_can_be_compared() { - assert_eq!(Zipf::new(1, 2.0), Zipf::new(1, 2.0)); + assert_eq!(Zipf::new(1.0, 2.0), Zipf::new(1.0, 2.0)); } } diff --git a/rand_distr/tests/cdf.rs b/rand_distr/tests/cdf.rs index 10d04e2156..62286860db 100644 --- a/rand_distr/tests/cdf.rs +++ b/rand_distr/tests/cdf.rs @@ -385,7 +385,7 @@ fn zipf() { let parameters = [(1000, 1.0), (500, 2.0), (1000, 0.5)]; for (seed, (n, x)) in parameters.into_iter().enumerate() { - let dist = rand_distr::Zipf::new(n, x).unwrap(); + let dist = rand_distr::Zipf::new(n as f64, x).unwrap(); test_discrete(seed as u64, dist, |k| cdf(k, n, x)); } } diff --git a/src/distr/uniform.rs b/src/distr/uniform.rs index 2ab4f56b82..ac3e1676ad 100644 --- a/src/distr/uniform.rs +++ b/src/distr/uniform.rs @@ -11,17 +11,7 @@ //! //! [`Uniform`] is the standard distribution to sample uniformly from a range; //! e.g. `Uniform::new_inclusive(1, 6).unwrap()` can sample integers from 1 to 6, like a -//! standard die. [`Rng::random_range`] supports any type supported by [`Uniform`]. -//! -//! This distribution is provided with support for several primitive types -//! (all integer and floating-point types) as well as [`std::time::Duration`], -//! and supports extension to user-defined types via a type-specific *back-end* -//! implementation. -//! -//! The types [`UniformInt`], [`UniformFloat`] and [`UniformDuration`] are the -//! back-ends supporting sampling from primitive integer and floating-point -//! ranges as well as from [`std::time::Duration`]; these types do not normally -//! need to be used directly (unless implementing a derived back-end). +//! standard die. [`Rng::random_range`] is implemented over [`Uniform`]. //! //! # Example usage //! @@ -151,26 +141,38 @@ use serde::{Deserialize, Serialize}; /// Sample values uniformly between two bounds. /// +/// # Construction +/// /// [`Uniform::new`] and [`Uniform::new_inclusive`] construct a uniform -/// distribution sampling from the given range; these functions may do extra -/// work up front to make sampling of multiple values faster. If only one sample -/// from the range is required, [`Rng::random_range`] can be more efficient. +/// distribution sampling from the given `low` and `high` limits. `Uniform` may +/// also be constructed via [`TryFrom`] as in `Uniform::try_from(1..=6).unwrap()`. +/// +/// Constructors may do extra work up front to allow faster sampling of multiple +/// values. Where only a single sample is required it is suggested to use +/// [`Rng::random_range`] or one of the `sample_single` methods instead. /// /// When sampling from a constant range, many calculations can happen at /// compile-time and all methods should be fast; for floating-point ranges and /// the full range of integer types, this should have comparable performance to /// the `Standard` distribution. /// -/// Steps are taken to avoid bias, which might be present in naive -/// implementations; for example `rng.gen::() % 170` samples from the range -/// `[0, 169]` but is twice as likely to select numbers less than 85 than other -/// values. Further, the implementations here give more weight to the high-bits -/// generated by the RNG than the low bits, since with some RNGs the low-bits -/// are of lower quality than the high bits. +/// # Provided implementations /// -/// Implementations must sample in `[low, high)` range for -/// `Uniform::new(low, high)`, i.e., excluding `high`. In particular, care must -/// be taken to ensure that rounding never results values `< low` or `>= high`. +/// - `char` ([`UniformChar`]): samples a range over the implementation for `u32` +/// - `f32`, `f64` ([`UniformFloat`]): samples approximately uniformly within a +/// range; bias may be present in the least-significant bit of the significand +/// and the limits of the input range may be sampled even when an open +/// (exclusive) range is used +/// - Integer types ([`UniformInt`]) may show a small bias relative to the +/// expected uniform distribution of output. In the worst case, bias affects +/// 1 in `2^n` samples where n is 56 (`i8` and `u8`), 48 (`i16` and `u16`), 96 +/// (`i32` and `u32`), 64 (`i64` and `u64`), 128 (`i128` and `u128`). +/// The `unbiased` feature flag fixes this bias. +/// - `usize` ([`UniformUsize`]) is handled specially, using the `u32` +/// implementation where possible to enable portable results across 32-bit and +/// 64-bit CPU architectures. +/// - `Duration` ([`UniformDuration`]): samples a range over the implementation +/// for `u32` or `u64` /// /// # Example /// diff --git a/src/distr/uniform_float.rs b/src/distr/uniform_float.rs index 82fd68bbc8..99bf2bf601 100644 --- a/src/distr/uniform_float.rs +++ b/src/distr/uniform_float.rs @@ -29,14 +29,17 @@ use serde::{Deserialize, Serialize}; /// /// # Implementation notes /// -/// Instead of generating a float in the `[0, 1)` range using [`Standard`], the -/// `UniformFloat` implementation converts the output of an PRNG itself. This -/// way one or two steps can be optimized out. +/// `UniformFloat` implementations convert RNG output to a float in the range +/// `[1, 2)` via transmutation, map this to `[0, 1)`, then scale and translate +/// to the desired range. Values produced this way have what equals 23 bits of +/// random digits for an `f32` and 52 for an `f64`. /// -/// The floats are first converted to a value in the `[1, 2)` interval using a -/// transmute-based method, and then mapped to the expected range with a -/// multiply and addition. Values produced this way have what equals 23 bits of -/// random digits for an `f32`, and 52 for an `f64`. +/// # Bias and range errors +/// +/// Bias may be expected within the least-significant bit of the significand. +/// It is not guaranteed that exclusive limits of a range are respected; i.e. +/// when sampling the range `[a, b)` it is not guaranteed that `b` is never +/// sampled. /// /// [`new`]: UniformSampler::new /// [`new_inclusive`]: UniformSampler::new_inclusive diff --git a/src/distr/uniform_int.rs b/src/distr/uniform_int.rs index b53ca367b9..4fe07707bd 100644 --- a/src/distr/uniform_int.rs +++ b/src/distr/uniform_int.rs @@ -58,6 +58,13 @@ use serde::{Deserialize, Serialize}; /// multiply by `range`, the result is in the high word. Then comparing the low /// word against `zone` makes sure our distribution is uniform. /// +/// # Bias +/// +/// Unless the `unbiased` feature flag is used, outputs may have a small bias. +/// In the worst case, bias affects 1 in `2^n` samples where n is +/// 56 (`i8` and `u8`), 48 (`i16` and `u16`), 96 (`i32` and `u32`), 64 (`i64` +/// and `u64`), 128 (`i128` and `u128`). +/// /// [`Uniform`]: super::Uniform #[derive(Clone, Copy, Debug, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] diff --git a/src/lib.rs b/src/lib.rs index 833fe0c0e4..f909292547 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -121,9 +121,9 @@ pub use rng::{Fill, Rng}; #[cfg(all(feature = "std", feature = "std_rng", feature = "getrandom"))] use crate::distr::{Distribution, Standard}; -/// Generates a random value using the thread-local random number generator. +/// Generate a random value using the thread-local random number generator. /// -/// This function is simply a shortcut for `rand::rng().gen()`: +/// This function is shorthand for [rng()].[random()](Rng::random): /// /// - See [`ThreadRng`] for documentation of the generator and security /// - See [`Standard`] for documentation of supported types and distributions @@ -142,21 +142,15 @@ use crate::distr::{Distribution, Standard}; /// } /// ``` /// -/// If you're calling `random()` in a loop, caching the generator as in the -/// following example can increase performance. +/// If you're calling `random()` repeatedly, consider using a local `rng` +/// handle to save an initialization-check on each usage: /// /// ``` -/// use rand::Rng; +/// use rand::Rng; // provides the `random` method /// -/// let mut v = vec![1, 2, 3]; -/// -/// for x in v.iter_mut() { -/// *x = rand::random() -/// } -/// -/// // can be made faster by caching rand::rng +/// let mut rng = rand::rng(); // a local handle to the generator /// -/// let mut rng = rand::rng(); +/// let mut v = vec![1, 2, 3]; /// /// for x in v.iter_mut() { /// *x = rng.random(); @@ -174,6 +168,127 @@ where rng().random() } +/// Return an iterator over [`random()`] variates +/// +/// This function is shorthand for +/// [rng()].[random_iter](Rng::random_iter)(). +/// +/// # Example +/// +/// ``` +/// let v: Vec = rand::random_iter().take(5).collect(); +/// println!("{v:?}"); +/// ``` +#[cfg(all(feature = "std", feature = "std_rng", feature = "getrandom"))] +#[inline] +pub fn random_iter() -> distr::DistIter +where + Standard: Distribution, +{ + rng().random_iter() +} + +/// Generate a random value in the given range using the thread-local random number generator. +/// +/// This function is shorthand for +/// [rng()].[random_range](Rng::random_range)(range). +/// +/// # Example +/// +/// ``` +/// let y: f32 = rand::random_range(0.0..=1e9); +/// println!("{}", y); +/// +/// let words: Vec<&str> = "Mary had a little lamb".split(' ').collect(); +/// println!("{}", words[rand::random_range(..words.len())]); +/// ``` +/// Note that the first example can also be achieved (without `collect`'ing +/// to a `Vec`) using [`seq::IteratorRandom::choose`]. +#[cfg(all(feature = "std", feature = "std_rng", feature = "getrandom"))] +#[inline] +pub fn random_range(range: R) -> T +where + T: distr::uniform::SampleUniform, + R: distr::uniform::SampleRange, +{ + rng().random_range(range) +} + +/// Return a bool with a probability `p` of being true. +/// +/// This function is shorthand for +/// [rng()].[random_bool](Rng::random_bool)(p). +/// +/// # Example +/// +/// ``` +/// println!("{}", rand::random_bool(1.0 / 3.0)); +/// ``` +/// +/// # Panics +/// +/// If `p < 0` or `p > 1`. +#[cfg(all(feature = "std", feature = "std_rng", feature = "getrandom"))] +#[inline] +#[track_caller] +pub fn random_bool(p: f64) -> bool { + rng().random_bool(p) +} + +/// Return a bool with a probability of `numerator/denominator` of being +/// true. +/// +/// That is, `random_ratio(2, 3)` has chance of 2 in 3, or about 67%, of +/// returning true. If `numerator == denominator`, then the returned value +/// is guaranteed to be `true`. If `numerator == 0`, then the returned +/// value is guaranteed to be `false`. +/// +/// See also the [`Bernoulli`] distribution, which may be faster if +/// sampling from the same `numerator` and `denominator` repeatedly. +/// +/// This function is shorthand for +/// [rng()].[random_ratio](Rng::random_ratio)(numerator, denominator). +/// +/// # Panics +/// +/// If `denominator == 0` or `numerator > denominator`. +/// +/// # Example +/// +/// ``` +/// println!("{}", rand::random_ratio(2, 3)); +/// ``` +/// +/// [`Bernoulli`]: distr::Bernoulli +#[cfg(all(feature = "std", feature = "std_rng", feature = "getrandom"))] +#[inline] +#[track_caller] +pub fn random_ratio(numerator: u32, denominator: u32) -> bool { + rng().random_ratio(numerator, denominator) +} + +/// Fill any type implementing [`Fill`] with random data +/// +/// This function is shorthand for +/// [rng()].[fill](Rng::fill)(dest). +/// +/// # Example +/// +/// ``` +/// let mut arr = [0i8; 20]; +/// rand::fill(&mut arr[..]); +/// ``` +/// +/// Note that you can instead use [`random()`] to generate an array of random +/// data, though this is slower for small elements (smaller than the RNG word +/// size). +#[cfg(all(feature = "std", feature = "std_rng", feature = "getrandom"))] +#[inline] +#[track_caller] +pub fn fill(dest: &mut T) { + dest.fill(&mut rng()) +} + #[cfg(test)] mod test { use super::*; @@ -200,4 +315,11 @@ mod test { (f32, (f64, (f64,))), ) = random(); } + + #[test] + #[cfg(all(feature = "std", feature = "std_rng", feature = "getrandom"))] + fn test_range() { + let _n: usize = random_range(42..=43); + let _f: f32 = random_range(42.0..43.0); + } } diff --git a/src/rng.rs b/src/rng.rs index a3657ed45f..9ac481ed9c 100644 --- a/src/rng.rs +++ b/src/rng.rs @@ -196,7 +196,9 @@ pub trait Rng: RngCore { } /// Return a bool with a probability of `numerator/denominator` of being - /// true. I.e. `random_ratio(2, 3)` has chance of 2 in 3, or about 67%, of + /// true. + /// + /// That is, `random_ratio(2, 3)` has chance of 2 in 3, or about 67%, of /// returning true. If `numerator == denominator`, then the returned value /// is guaranteed to be `true`. If `numerator == 0`, then the returned /// value is guaranteed to be `false`. diff --git a/src/rngs/thread.rs b/src/rngs/thread.rs index fca961f532..64ca0e1f76 100644 --- a/src/rngs/thread.rs +++ b/src/rngs/thread.rs @@ -41,16 +41,36 @@ const THREAD_RNG_RESEED_THRESHOLD: u64 = 1024 * 64; /// A reference to the thread-local generator /// /// This type is a reference to a lazily-initialized thread-local generator. -/// An instance can be obtained via [`rand::rng()`][crate::rng())] or via -/// `ThreadRng::default()`. +/// An instance can be obtained via [`rand::rng()`][crate::rng()] or via +/// [`ThreadRng::default()`]. /// The handle cannot be passed between threads (is not `Send` or `Sync`). /// -/// `ThreadRng` uses the same CSPRNG as [`StdRng`], ChaCha12. As with -/// [`StdRng`], the algorithm may be changed, subject to reasonable expectations -/// of security and performance. +/// # Security /// -/// `ThreadRng` is automatically seeded from [`OsRng`] with periodic reseeding -/// (every 64 kiB — see [`ReseedingRng`] documentation for details). +/// Security must be considered relative to a threat model and validation +/// requirements. The Rand project can provide no guarantee of fitness for +/// purpose. The design criteria for `ThreadRng` are as follows: +/// +/// - Automatic seeding via [`OsRng`] and periodically thereafter (see +/// ([`ReseedingRng`] documentation). Limitation: there is no automatic +/// reseeding on process fork (see [below](#fork)). +/// - A rigorusly analyzed, unpredictable (cryptographic) pseudo-random generator +/// (see [the book on security](https://rust-random.github.io/book/guide-rngs.html#security)). +/// The currently selected algorithm is ChaCha (12-rounds). +/// See also [`StdRng`] documentation. +/// - Not to leak internal state through [`Debug`] or serialization +/// implementations. +/// - No further protections exist to in-memory state. In particular, the +/// implementation is not required to zero memory on exit (of the process or +/// thread). (This may change in the future.) +/// - Be fast enough for general-purpose usage. Note in particular that +/// `ThreadRng` is designed to be a "fast, reasonably secure generator" +/// (where "reasonably secure" implies the above criteria). +/// +/// We leave it to the user to determine whether this generator meets their +/// security requirements. For an alternative, see [`OsRng`]. +/// +/// # Fork /// /// `ThreadRng` is not automatically reseeded on fork. It is recommended to /// explicitly call [`ThreadRng::reseed`] immediately after a fork, for example: @@ -68,13 +88,6 @@ const THREAD_RNG_RESEED_THRESHOLD: u64 = 1024 * 64; /// from an interrupt (e.g. a fork handler) unless it can be guaranteed that no /// other method on the same `ThreadRng` is currently executing. /// -/// Security must be considered relative to a threat model and validation -/// requirements. `ThreadRng` attempts to meet basic security considerations -/// for producing unpredictable random numbers: use a CSPRNG, use a -/// recommended platform-specific seed ([`OsRng`]), and avoid -/// leaking internal secrets e.g. via [`Debug`] implementation or serialization. -/// Memory is not zeroized on drop. -/// /// [`ReseedingRng`]: crate::rngs::ReseedingRng /// [`StdRng`]: crate::rngs::StdRng #[derive(Clone)] @@ -115,9 +128,9 @@ thread_local!( } ); -/// Access a local, pre-initialized generator +/// Access a fast, pre-initialized generator /// -/// This is a reasonably fast unpredictable thread-local instance of [`ThreadRng`]. +/// This is a handle to the local [`ThreadRng`]. /// /// See also [`crate::rngs`] for alternatives. /// @@ -139,6 +152,10 @@ thread_local!( /// println!("A simulated die roll: {}", rng.random_range(1..=6)); /// # } /// ``` +/// +/// # Security +/// +/// Refer to [`ThreadRng#Security`]. pub fn rng() -> ThreadRng { let rng = THREAD_RNG_KEY.with(|t| t.clone()); ThreadRng { rng } diff --git a/src/seq/iterator.rs b/src/seq/iterator.rs index ad96b3baf7..b10d205676 100644 --- a/src/seq/iterator.rs +++ b/src/seq/iterator.rs @@ -54,6 +54,15 @@ pub trait IteratorRandom: Iterator + Sized { /// Consider instead using [`IteratorRandom::choose_stable`] to avoid /// [`Iterator`] combinators which only change size hints from affecting the /// results. + /// + /// # Example + /// + /// ``` + /// use rand::seq::IteratorRandom; + /// + /// let words = "Mary had a little lamb".split(' '); + /// println!("{}", words.choose(&mut rand::rng()).unwrap()); + /// ``` fn choose(mut self, rng: &mut R) -> Option where R: Rng + ?Sized,