The Rust Rand Book

This is the extended documentation for Rust's Random number library.

This book contains:

  1. An overview of crates and functionality
  2. Documentation of crate feature flags
  3. The Users' Guide
  4. Notes on Portability and Reproducibility. (Read this if you want reproducibility across builds.)
  5. Updating guides
  6. Contributor's guide

Outside this book, you may want:

Crates

The Rand library consists of a family of crates. For common usage, the rand crate alone will often suffice. Other crates serve as building-blocks and/or provide additional functionality.

rand_core

The rand_core crate defines the core traits implemented by RNGs. This exists as a separate crate with two purposes:

  • to provide a minimal API for defining and using RNGs
  • to provide tools to aid implementation of RNGs

The RngCore, SeedableRng, CryptoRng traits and Error type are all defined by this crate and re-exported by the rand crate.

rand

The rand crate is optimised for easy usage of common random-number functionality. This has several aspects:

  • the rngs module provides a few convenient generators
  • the distributions module concerns sampling of random values
  • the seq module concerns sampling from and shuffling sequences
  • the Rng trait provides a few convenience methods for generating random values
  • the random function provides convenient generation in a single call

Distributions

The rand crate only implements sampling from the most common random number distributions: uniform and weighted sampling. For everything else,

  • rand_distr provides fast sampling from a variety of other distributions, including Normal (Gauss), Binomial, Poisson, UnitCircle, and many more
  • statrs is a port of the C# Math.NET library, implementing many of the same distributions (plus/minus a few), along with PDF and CDF functions, the error, beta, gamma and logistic special functions, plus a few utilities. (For clarity, statrs is not part of the Rand library.)

Generators

Deterministic generators

The following crates implement [PRNGs]:

  • rand_chacha provides generators using the ChaCha cipher
  • rand_hc implements a generator using the HC-128 cipher
  • rand_isaac implements the ISAAC generators
  • rand_pcg implements a small selection of PCG generators
  • rand_xoshiro implements the SplitMix and Xoshiro generators
  • rand_xorshift implements the basic Xorshift generator

Non-deterministic generators

The following crates provide non-deterministic random data:

  • getrandom provides an interface to system-specific random data sources
  • rand_os provides a simple RNG wrapping getrandom functionality (this wrapper is duplicated by the rand crate)
  • rand_jitter implements a CPU-jitter-based entropy harvestor

Feature flags

Some functionality is gated behind Cargo features, which must be enabled as required, e.g.

rand = { version = "0.7", features = ["log", "serde1"] }

Small RNG

From Rand version 0.7, the SmallRng type is gated behind the small_rng feature flag (this reduces rand's depnedency count by one if not used).

Logging

Rand can optionally log a few events (mostly related to reseeding of ThreadRng and to rand_jitter).

Serde

Some parts of the Rand lib support serialisation via Serde. To enable this, use the serde1 feature.

Note that cryptographic RNGs do not support serialisation since this could be a security risk. If you need state-restore functionality on a cryptographic RNG, the ChaCha generator supports getting and setting the stream position, which, together with the seed, can be used to reconstruct the generator's state.

SIMD support

Experimental support for generating SIMD values is available under the simd_support feature gate. This requires nightly Rust.

The ChaCha implementations provided by rand_chacha use SIMD operations internally even on stable Rust. This is enabled by default.

No-std mode

Almost all Rand libraries can be used without Rust's standard library:

  • For the rand and rand_chacha crates, one must disable default features (default-features = false) and some functionality is lost.
  • The rand_core and rand_jitter crates assume no_std by default, but support some additional functionality when the std feature is enabled.
  • getrandom (and by extension rand_os) has limited support for no_std. It does not use feature flags, instead detecting requirements based on the target platform.
  • The rand_distr crate does not support no_std mode.
  • All PRNG crates other than rand_chacha do not require std.

If an allocator is available, some functionality can be recovered by use of the alloc feature. On:

  • rand_core, the alloc feature enables blanket trait impls for Box<T>
  • rand, the alloc feature enables sequence-related functionality

Guide

This section attempts to explain some of the concepts used in this library.

  1. Intro: Hello Random example
  2. What is random data and what is randomness anyway?
  3. What kind of random generators are there?
  4. What random number generators does Rand provide?
  5. Turning random data into useful values
  6. Distributions: more control over random values
  7. Sequences
  8. Error handling

Importing items (prelude)

The most convenient way to import items from Rand is to use the prelude. This includes the most important parts of Rand, but only those unlikely to cause name conflicts.

Note that Rand 0.5 has significantly changed the module organization and contents relative to previous versions. Where possible old names have been kept (but are hidden in the documentation), however these will be removed in the future. We therefore recommend migrating to use the prelude or the new module organization in your imports.

Further examples

For some inspiration, see the example applications:

Getting Started

Lets kick things off with an example (playground link):

# extern crate rand;

// import commonly used items from the prelude:
use rand::prelude::*;

fn main() {
    // We can use random() immediately. It can produce values of many common types:
    let x: u8 = random();
    println!("{}", x);

    if random() { // generates a boolean
        println!("Heads!");
    }

    // If we want to be a bit more explicit (and a little more efficient) we can
    // make a handle to the thread-local generator:
    let mut rng = thread_rng();
    if rng.gen() { // random bool
        let x: f64 = rng.gen(); // random number in range [0, 1)
        let y = rng.gen_range(-10.0, 10.0);
        println!("x is: {}", x);
        println!("y is: {}", y);
        println!("Number from 0 to 9: {}", rng.gen_range(0, 10));
    }
    
    // Sometimes it's useful to use distributions directly:
    let distr = rand::distributions::Uniform::new_inclusive(1, 100);
    let mut nums = [0i32; 3];
    for x in &mut nums {
        *x = rng.sample(distr);
    }
    println!("Some numbers: {:?}", nums);
    
    // We can also interact with iterators and slices:
    let arrows_iter = "➡⬈⬆⬉⬅⬋⬇⬊".chars();
    println!("Lets go in this direction: {}", arrows_iter.choose(&mut rng).unwrap());
    let mut nums = [1, 2, 3, 4, 5];
    nums.shuffle(&mut rng);
    println!("I shuffled my {:?}", nums);
}

The first thing you may have noticed is that we imported everything from the prelude. This is the lazy way to use rand, and like the standard library's prelude, only imports the most common items. If you don't wish to use the prelude, remember to import the Rng trait!

The Rand library automatically initialises a secure, thread-local generator on demand. This can be accessed via the thread_rng and random functions. For more on this topic, see Random generators.

While the random function can only sample values in a Standard (type-dependent) manner, thread_rng gives you a handle to a generator. All generators implement the Rng trait, which provides the gen, gen_range and sample methods used above.

Rand provides functionality on iterators and slices via two more traits, IteratorRandom and SliceRandom.

Random data


# #![allow(unused_variables)]
#fn main() {
# extern crate rand;
# use rand::RngCore;
// get some random data:
let mut data = [0u8; 32];
rand::thread_rng().fill_bytes(&mut data);
#}

What is randomness?

What does random mean? Colloquially the word can mean simply unexpected or unknown, but we need to be a bit more precise than that. Wikipedia gives us a more specific definition:

Randomness is the lack of pattern or predictability in events.

We can take this further: lack of pattern implies there is no bias; in other words, all possible values are equally likely.

To understand what a random value is, we still need a context: what pool of numbers can our random value come from?

  • To give a simple example, consider dice: they have values 1, 2, 3, 4, 5 and 6, and an unbiased (fair) die will make each number equally likely, with probability ⅙th.
  • Now lets take a silly example: the natural numbers (1, 2, 3, etc.). These numbers have no limit. So if you were to ask for an unbiased random natural number, 1, 5, 1000, 1 million, 1 trillion — all would be equally likely. In fact, for any natural number k, the numbers 1, 2, ..., k are an infinitely small fraction of all the natural numbers, which means the chance of picking a unbiased number from this range is effectively 1/∞ = 0. Put another way: for any natural number, we expect an unbiased random value to be bigger. This is impossible, so there cannot be any such thing as an unbiased random natural number.
  • Another example: real numbers between 0 and 1. Real numbers include all the fractions, irrational numbers like π and √2, and all multiples of those... there are infinitely many possibilities, even in a small range like (0, 1), so simply saying "all possibilities are equally likely" is not enough. Instead we interpret lack of pattern in a different way: every interval of equal size is equally likely; for example we could subdivide the interval 0,1 into 0,½ and ½,1 and toss a coin to decide which interval our random sample comes from. Say we pick ½,1 we can then toss another coin to decide between ½,¾ and ¾,1, restricting our random value to an interval of size ¼. We can repeat this as many times as necessary to pick a random value between 0 and 1 with as much precision as we want — although we should realise that we are not choosing an exact value but rather just a small interval.

What we have defined (or failed to define) above are uniform random number distributions, or simply uniform distributions. There are also non-uniform distributions, as we shall see later. It's also worth noting here that a uniform distribution does not imply that its samples will be evenly spread (try rolling six dice: you probably won't get 1, 2, 3, 4, 5, 6).

To bring us back to computing, we can now define what a uniformly distributed random value (an unbiased random value) is in several contexts:

  • u32: a random number between 0 and u32::MAX where each value is equally likely
  • BigInt: since this type has no upper bound, we cannot produce an unbiased random value (it would be infinitely large, and use infinite amounts of memory)
  • f64: we treat this as an approximation of the real numbers, and, by convention, restrict to the range 0 to 1 (if not otherwise specified). Note that this type has finite precision, so we use the coin-flipping method above (but with random bits instead of coins) until we get as much precision as the type can represent; however, since floating-point numbers are much more precise close to 0 than they are near 1, we typically simplify here and stop once we have enough precision to differentiate between 1 and the next smallest value representable (1 - ε/2).

Random data

As seen above, the term "random number" is meaningless without context. "Random data" typically means a sequence of random bytes, where for each byte, each of the 256 possible values are equally likely.

RngCore::fill_bytes produces exactly this: a sequence of random bytes.

If a sequence of unbiased random bytes of the correct length is instead interpreted as an integer — say a u32 or u64 — the result is an unbiased integer. Since this conversion is trivial, RngCore::next_u32 and RngCore::next_u64 are part of the same trait. (In fact the conversion is often the other way around — algorithmic generators usually work with integers internally, which are then converted to whichever form of random data is required.)

Random generators

The previous section introduced RngCore, the trait which all random data sources must implement. But what exactly is a random data source?

This section concerns theory; see also the chapter on random number generators.


# #![allow(unused_variables)]
#fn main() {
# extern crate rand;
# extern crate rand_pcg;
// prepare a non-deterministic random number generator:
let mut rng = rand::thread_rng();

// prepare a deterministic generator:
use rand::SeedableRng;
let mut rng = rand_pcg::Pcg32::seed_from_u64(123);
#}

True random number generators

A true random number generator (TRNG) is something which produces random numbers by observing some natural process, such as atomic decay or thermal noise. (Whether or not these things are truly random or are in fact deterministic — for example if the universe itself is a simulation — is besides the point here. For our purposes, it is sufficient that they are not distinguishable from true randomness.)

Note that these processes are often biased, thus some type of debiasing must be used to yield the unbiased random data we desire.

Pseudo-random number generators

CPUs are of course supposed to compute deterministically, yet it turns out they can do a pretty good job of emulating random processes. Most pseudo-random number generators are deterministic and can be defined by three things:

  • some initial state
  • a function to compute a random value from the state
  • a function to advance to the next state

The fact that these are deterministic can sometimes be very useful: it allows a simulation, randomised art work or game to be repeated exactly, producing a result which is a function of the seed. For more on this see the portability chapter (note that determinicity alone isn't enough to guarantee reproducibility).

The other big attraction of PRNGs is their speed: some of these algorithms require only a few CPU operations per random value, and thus can produce random data on demand much more quickly than most TRNGs.

Note however that PRNGs have several limitations:

  • They are no stronger than their seed: if the seed is known or guessable, and the algorithm is known (or guessed), then only a small number of output sequences are likely.
  • Since the state size is usually fixed, only a finite number of output values are possible before the generator loops and repeats itself.
  • Several algorithms are easily predictable after seeing a few values, and with many other algorithms it is not clear whether they could be "cracked".

Cryptographically secure pseudo-random number generator

Cryptographically secure pseudo-random number generators (CSPRNGs) are the subset of PRNGs which are considered secure. That is:

  • their state is sufficiently large that a brute-force approach simply trying all initial values is not a feasible method of finding the initial state used to produce an observed sequence of output values,
  • and there is no other algorithm which is sufficiently better than the brute-force method which would make it feasible to predict the next output value.

Achieving secure generation requires not only a secure algorithm (CSPRNG), but also a secure and sufficiently large seed value (typically 256 bits), and protection against side-channel attacks (i.e. preventing attackers from reading the internal state).

Some CSPRNGs additionally satisfy a third property:

  • a CSPRNG is backtracking resistant if it is impossible for an attacker to calculate prior output values of the PRNG despite having discovered the value of the current internal state (implying that all future output is compromised).

Hardware random number generator

A hardware random number generator (HRNG) is theoretically an adaptor from some TRNG to digital information. In practice, these may use a PRNG to debias the TRNG. Even though an HRNG has some underlying TRNG, it is not guaranteed to be secure: the TRNG itself may produce insufficient entropy (i.e. be too predictable), or the signal amplification and debiasing process may be flawed.

An HRNG may be used to provide the seed for a PRNG, although usually this is not the only way to obtain a secure seed (see the next section). An HRNG might replace a PRNG altogether, although since we now have very fast and very strong software PRNGs, and since software implementations are easier to verify than hardware ones, this is often not the preferred solution.

Since a PRNG needs a random seed value to be secure, an HRNG may be used to provide that seed, or even replace the need for a PRNG. However, since the goal is usually "only" to produce unpredictable random values, there are acceptable alternatives to true random number generators (see next section).

Entropy

As noted above, for a CSPRNG to be secure, its seed value must also be secure. The word entropy can be used in two ways:

  • as a measure of the amount of unknown information in some piece of data
  • as a piece of unknown data

Ideally, a random boolean or a coin flip has 1 bit of entropy, although if the value is biased, there will be less. Shannon Entropy attempts to measure this.

For example, a Unix time-stamp (seconds since the start of 1970) contains both high- and low-resolution data. This is typically a 32-bit number, but the amount of entropy will depend on how precisely a hypothetical attacker can guess the number. If an attacker can guess the number to the nearest minute, this may be approximately 6 bits (2^6 = 64); if an attacker can guess this to the second, this is 0 bits. JitterRng uses this concept to scavenge entropy without an HRNG (but using nanosecond resolution timers and conservatively assuming only a couple of bits entropy is available per time-stamp, after running several tests on the timer's quality).

Our RNGs

There are many kinds of RNGs, with different trade-offs. Rand provides some convenient generators in the rngs module. Often you can just use thread_rng, a function which automatically initializes an RNG in thread-local memory and returns a reference to it. It is fast, good quality, and (to the best of our knowledge) cryptographically secure.

Contents of this documentation:

  1. The generators
  2. Performance and size
  3. Quality and cycle length
  4. Security
  5. Extra features
  6. Further reading

The generators

Basic pseudo-random number generators (PRNGs)

The goal of regular, non-cryptographic PRNGs is usually to find a good balance between simplicity, quality, memory usage and performance. Non-cryptographic generators pre-date cryptographic ones and since we now have fast cryptographic generators, some people argue that the non-cryptographic ones are now obsolete. They can however have some advantages: small state size, fast initialisation and simplicity (though this is not true of all non-crypto PRNGs; e.g. the Mersenne Twister has a large state despite being easy to predict).

These algorithms are very important to Monte Carlo simulations, and also suitable for several other problems such as randomized algorithms and games, where predictability is not an issue. (Note that it might be problematic for betting games and multiplayer games, where a cryptographic PRNG is usually more appropriate.)

The Rand project provides several non-cryptographic PRNGs. A sub-set of these are summarised below. You may wish to refer to the pcg-random and xoshiro websites.

name full name performance memory quality period features
SmallRng (unspecified) 7 GB/s 16 bytes ★★★☆☆ u32 * 264 not portable
Pcg32 PCG XSH RR 64/32 (LCG) 3 GB/s 16 bytes ★★★☆☆ u32 * 264
Pcg64 PCG XSL 128/64 (LCG) 4 GB/s 32 bytes ★★★★☆ u64 * 2128
Pcg64Mcg PCG XSL 128/64 (MCG) 7 GB/s 16 bytes ★★★☆☆ u64 * 2126
XorShiftRng Xorshift 32/128 5 GB/s 16 bytes ★☆☆☆☆ u32 * 2128 - 1
Xoshiro256StarStar Xoshiro256** 7 GB/s 32 bytes ★★★☆☆ u64 * 2256 - 1 jump-ahead
Xoshiro256Plus Xoshiro256+ 8 GB/s 32 bytes ★★☆☆☆ u64 * 2256 - 1 jump-ahead
SplitMix64 splitmix64 8 GB/s 8 bytes ★☆☆☆☆ u64 * 264
StepRng counter 51 GB/s 16 bytes ☆☆☆☆☆ u64 * 264

Here, performance is measured roughly for u64 outputs on a 3.4GHz Haswell CPU (note that this will vary significantly by application; in general cryptographic RNGs do better with byte sequence output). Quality ratings are based on theory and observable defects, roughly as follows:

  • ★☆☆☆☆ = suitable for simple applications but with significant flaws
  • ★★☆☆☆ = good performance in most tests, some issues
  • ★★★☆☆ = good performance and theory, no major issues
  • ★★★★☆ = no observable issues and not trivial to predict (but not recommended for security)
  • ★★★★★ = cryptographic quality

Cryptographically secure pseudo-random number generators (CSPRNGs)

CSPRNGs have much higher requirements than basic PRNGs. The primary consideration is security. Performance and simplicity are also important, but in general CSPRNGs are more complex and slower than regular PRNGs. Quality is no longer a concern, as it is a requirement for a CSPRNG that the output is basically indistinguishable from true randomness since any bias or correlation makes the output more predictable.

There is a close relationship between CSPRNGs and cryptographic ciphers. Any block cipher can be turned into a CSPRNG by encrypting a counter. Stream ciphers are basically a CSPRNG and a combining operation, usually XOR. This means that we can easily use any stream cipher as a CSPRNG.

This library provides the following CSPRNGs. We can make no guarantees of any security claims. This table omits the "quality" column from the previous table since CSPRNGs may not have observable defects.

name full name performance initialization memory security (predictability) forward secrecy
StdRng (unspecified) 1.5 GB/s fast 136 bytes widely trusted no
ChaCha20Rng ChaCha20 1.8 GB/s fast 136 bytes rigorously analysed no
ChaCha8Rng ChaCha8 2.2 GB/s fast 136 bytes small security margin no
Hc128Rng HC-128 2.1 GB/s slow 4176 bytes recommended by eSTREAM no
IsaacRng ISAAC 1.1 GB/s slow 2072 bytes unknown unknown
Isaac64Rng ISAAC-64 2.2 GB/s slow 4136 bytes unknown unknown

It should be noted that the ISAAC generators are only included for historical reasons, they have been with the Rust language since the very beginning. They have good quality output and no attacks are known, but have received little attention from cryptography experts.

Notes on generators

Performance

First it has to be said most PRNGs are very fast, and will rarely be a performance bottleneck.

Performance of basic PRNGs is a bit of a subtle thing. It depends a lot on the CPU architecture (32 vs. 64 bits), inlining, and also on the number of available registers. This often causes the performance to be affected by surrounding code due to inlining and other usage of registers.

When choosing a PRNG for performance it is important to benchmark your own application due to interactions between PRNGs and surrounding code and dependence on the CPU architecture as well as the impact of the size of data requested. Because of all this, we do not include performance numbers here but merely a qualitative rating.

CSPRNGs are a little different in that they typically generate a block of output in a cache, and pull outputs from the cache. This allows them to have good amortised performance, and reduces or completely removes the influence of surrounding code on the CSPRNG performance.

Worst-case performance

Simple PRNGs typically produce each random value on demand. In contrast, CSPRNGs usually produce a whole block at once, then read from this cache until it is exhausted, giving them much less consistent performance when drawing small quantities of random data.

Memory usage

Simple PRNGs often use very little memory, commonly only a few words, where a word is usually either u32 or u64. This is not true for all non-cryptographic PRNGs however, for example the historically popular Mersenne Twister MT19937 algorithm requires 2.5 kB of state.

CSPRNGs typically require more memory; since the seed size is recommended to be at least 192 bits and some more may be required for the algorithm, 256 bits would be approximately the minimum secure size. In practice, CSPRNGs tend to use quite a bit more, ChaChaRng is relatively small with 136 bytes of state.

Initialization time

The time required to initialize new generators varies significantly. Many simple PRNGs and even some cryptographic ones (including ChaChaRng) only need to copy the seed value and some constants into their state, and thus can be constructed very quickly. In contrast, CSPRNGs with large state require an expensive key-expansion.

Quality

Many basic PRNGs are not much more than a couple of bitwise and arithmetic operations. Their simplicity gives good performance, but also means there are small regularities hidden in the generated random number stream.

How much do those hidden regularities matter? That is hard to say, and depends on how the RNG gets used. If there happen to be correlations between the random numbers and the algorithm they are used in, the results can be wrong or misleading.

A random number generator can be considered good if it gives the correct results in as many applications as possible. The quality of PRNG algorithms can be evaluated to some extend analytically, to determine the cycle length and to rule out some correlations. Then there are empirical test suites designed to test how well a PRNG performs on a wide range of possible uses, the latest and most complete of which are TestU01 and PractRand.

CSPRNGs tend to be more complex, and have an explicit requirement to be unpredictable. This implies there must be no obvious correlations between output values.

Quality stars:

PRNGs with 3 stars or more should be good enough for most non-crypto applications. 1 or 2 stars may be good enough for typical apps and games, but do not work well with all algorithms.

Period

The period or cycle length of a PRNG is the number of values that can be generated after which it starts repeating the same random number stream. Many PRNGs have a fixed-size period, but for some only an expected average cycle length can be given, where the exact length depends on the seed.

On today's hardware, even a fast RNG with a cycle length of only 264 can be used sequentially for centuries before cycling. However, this is not the case for parallel applications. We recommend a period of 2128 or more, which most modern PRNGs satisfy. Alternatively a PRNG with shorter period but support for multiple streams may be chosen. There are two reasons for this, as follows.

If we see the entire period of an RNG as one long random number stream, every independently seeded RNG returns a slice of that stream. When multiple RNG are seeded randomly, there is an increasingly large chance to end up with a partially overlapping slice of the stream.

If the period of the RNG is 2128, and an application consumes 248 values, it then takes about 232 random initializations to have a chance of 1 in a million to repeat part of an already used stream. This seems good enough for common usage of non-cryptographic generators, hence the recommendation of at least 2128. As an estimate, the chance of any overlap in a period of size p with n independent seeds and u values used per seed is approximately 1 - e^(-u * n^2 / (2 * p)).

Further, it is not recommended to use the full period of an RNG. Many PRNGs have a property called k-dimensional equidistribution, meaning that for values of some size (potentially larger than the output size), all possible values are produced the same number of times over the generator's period. This is not a property of true randomness. This is known as the generalized birthday problem, see the PCG paper for a good explanation. This results in a noticable bias on output after generating more values than the square root of the period (after 264 values for a period of 2128).

Security

Predictability

From the context of any PRNG, one can ask the question given some previous output from the PRNG, is it possible to predict the next output value? This is an important property in any situation where there might be an adversary.

Regular PRNGs tend to be predictable, although with varying difficulty. In some cases prediction is trivial, for example plain Xorshift outputs part of its state without mutation, and prediction is as simple as seeding a new Xorshift generator from four u32 outputs. Other generators, like PCG and truncated Xorshift* are harder to predict, but not outside the realm of common mathematics and a desktop PC.

The basic security that CSPRNGs must provide is the infeasibility to predict output. This requirement is formalized as the next-bit test; this is roughly stated as: given the first k bits of a random sequence, the sequence satisfies the next-bit test if there is no algorithm able to predict the next bit using reasonable computing power.

A further security that some CSPRNGs provide is forward secrecy: in the event that the CSPRNGs state is revealed at some point, it must be infeasible to reconstruct previous states or output. Note that many CSPRNGs do not have forward secrecy in their usual formulations.

Verifying security claims of an algorithm is a hard problem, and we are not able to provide any guarantees of the security of algorithms used or recommended by this project. We refer you to the NIST institute and ECRYPT network for recommendations.

State and seeding

It is worth noting that a CSPRNG's security relies absolutely on being seeded with a secure random key. Should the key be known or guessable, all output of the CSPRNG is easy to guess. This implies that the seed should come from a trusted source; usually either the OS or another CSPRNG. Our seeding helper trait, FromEntropy, and the source it uses (EntropyRng), should be secure. Additionally, ThreadRng is a CSPRNG, thus it is acceptable to seed from this (although for security applications fresh/external entropy should be preferred).

Further, it should be obvious that the internal state of a CSPRNG must be kept secret. With that in mind, our implementations do not provide direct access to most of their internal state, and Debug implementations do not print any internal state. This does not fully protect CSPRNG state; code within the same process may read this memory (and we allow cloning and serialisation of CSPRNGs for convenience). Further, a running process may be forked by the operating system, which may leave both processes with a copy of the same generator.

Not a crypto library

It should be emphasised that this is not a cryptography library; although Rand does take some measures to provide secure random numbers, it does not necessarily take all recommended measures. Further, cryptographic processes such as encryption and authentication are complex and must be implemented very carefully to avoid flaws and resist known attacks. It is therefore recommended to use specialized libraries where possible, for example openssl, ring and the RustCrypto libraries.

Extra features

Some PRNGs may provide extra features, like:

  • Support for multiple streams, which can help with parallel tasks.
  • The ability to jump or seek around in the random number stream; with a large period this can be used as an alternative to streams.

Further reading

There is quite a lot that can be said about PRNGs. The PCG paper is very approachable and explains more concepts.

Another good paper about RNG quality is "Good random number generators are (not so) easy to find" by P. Hellekalek.

Random values

Now that we have a way of producing random data, how can we convert it to the type of value we want?

This is a trick question: we need to know both the range we want and the type of distribution of this value (which is what the next section is all about).

For convenience, all generators automatically implement the Rng trait, which provides short-cuts to a few ways of generating values. This has several convenience functions for producing uniformly distributed values:

  • gen generates an unbiased random value from a range appropriate for the type. For integers this is normally the full representable range (e.g. from 0u32 to std::u32::MAX), for floats this is between 0 and 1, and some other types are supported, including arrays and tuples. More on this in the next section.
  • gen_range generates an unbiased random value with given bounds low (inclusive) and high (exclusive)
  • fill and try_fill are optimised functions for filling any byte or integer slice with random values

It also has convenience functions for producing non-uniform boolean values:

  • gen_bool generates a boolean with the given probability
  • gen_ratio also generates a boolean, where the probability is defined via a fraction

Finally, it has a function to sample from arbitrary distributions:

Examples:


# #![allow(unused_variables)]
#fn main() {
# extern crate rand;
use rand::Rng;
let mut rng = rand::thread_rng();

// an unbiased integer over the entire range:
let i: i32 = rng.gen();

// a uniformly distributed value between 0 and 1:
let x: f64 = rng.gen();

// simulate rolling a die:
let roll = rng.gen_range(1, 7);
#}

Additionally, the random function is a short-cut to gen on the thread_rng:


# #![allow(unused_variables)]
#fn main() {
if rand::random() {
    println!("we got lucky!");
}
#}

Random distributions

For maximum flexibility when producing random values, we define the Distribution trait:


# #![allow(unused_variables)]
#fn main() {
// a producer of data of type T:
pub trait Distribution<T> {
    // the key function:
    fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> T;

    // a convenience function defined using sample:
    fn sample_iter<'a, R>(&'a self, rng: &'a mut R) -> DistIter<'a, Self, R, T>
    where
        Self: Sized,
        R: Rng,
    { ... }
}
#}

Rand provides implementations of many different distributions; for the full list see the distributions module; the most common are highlighted below.

Uniform distributions

The most obvious type of distribution is the one we already discussed: one without pattern, where each value or range of values is equally likely. This is known as uniform.

Rand actually has several variants of this:

  • Standard requires no parameters and produces uniformly distributed values over the entire range of the output type (for bool and integers) or over the range from 0 to 1 (for floats) or over valid Unicode code points. It also has extensions to tuples, array types and Option.
  • Uniform is parametrised with low and high points, and produces values uniformly distributed within this range.
  • Alphanumeric is uniform over the values 0-9A-Za-z
  • Open01 and OpenClosed01 are variations of Standard for floating point numbers between 0 and 1 (partially) exclusive of end points.

For convenience, Rng::gen and random are short-cuts to Standard, and Rng::gen_range is a short-cut to Uniform, allowing things like:


# #![allow(unused_variables)]
#fn main() {
# extern crate rand;
# use rand::prelude::*;
let mut rng = thread_rng();
let coord: (f64, f64) = rng.gen();
let die_roll = rng.gen_range(1, 7);
#}

More continuous distributions

The exponential distribution, Exp, simulates time until decay, assuming a fixed rate of decay (i.e. exponential decay).

The Normal distribution (also known as Gaussian) simulates sampling from the Normal distribution ("Bell curve") with the given mean and standard deviation. The LogNormal is related: for sample X from the log-normal distribution, log(X) is normally distributed; this "skews" the normal distribution to avoid negative values and to have a long positive tail.

The UnitCircle and UnitSphereSurface distributions simulate uniform sampling from the edge of a circle or surface of a sphere.

The Cauchy distribution (also known as the Lorentz distribution) is the distribution of the x-intercept of a ray from point (x0, γ) with uniformly distributed angle.

The Beta distribution is a two-parameter probability distribution, whose output values lie between 0 and 1. The Dirichlet distribution is a generalisation to any positive number of parameters.

Discrete distributions

The Bernoulli distribution is very simple: given a probability p (or a ratio num / denom), a boolean value is produced with the given probability of being true (simulating a trial with probability p of success).

For convenience, Rng::gen_bool and Rng::gen_ratio are short-cuts to Bernoulli.

The Binomial distribution is related: given a probability p and a number n, this distribution simulates running n Bernoulli trials and tells you the number which were successful.

The Poisson distribution expresses the expected number of events occurring within a fixed interval, given that events occur with fixed rate λ.

Weighted sampling

Finally, WeightedIndex is a discrete distribution sampling from a finite selection of choices each with given weight.

Sequences

Rand implements a few common random operations on sequences via the IteratorRandom and SliceRandom traits:

  • choose one element uniformly from the sequence
  • choose_multiple elements uniformly without replacement
  • choose_weighted — choose an element non-uniformly by use of a defined weight from a slice (also see the WeightedIndex distribution)
  • shuffle a slice
  • partial_shuffle a slice, effectively extracting amount elements in random order

Error handling

Error handling in Rand is a compromise between simplicity and necessity. Most RNGs and sampling functions will never produce errors, and making these able to handle errors would add significant overhead (to code complexity and ergonomics of usage at least, and potentially also performance, depending on the approach). However, external RNGs can fail, and being able to handle this is important.

It has therefore been decided that most methods should not return a Result type, but with a few important exceptions, namely:

Most functions consuming random values will not attempt any error handling, and reduce to calls to RngCore's "infallible" methods. Since most RNGs cannot fail anyway this is usually not a problem, but the few generators which can may be forced to fail in this case:

  • OsRng interfaces with the Operating System's generator; in rare cases this may fail as "not ready" or simply "unavailable".
  • JitterRng is a generator based on timer jitter; if the timer does not appear to be capable of sufficient precision or is too predictable, this will fail.
  • EntropyRng is an abstraction over the above, falling back to the next option when the first fails but ultimately failing if all sources fail
  • thread_rng seeds itself via EntropyRng, thus can potentially fail on its first use on each thread (though it never fails after the first use)
  • ReadRng tries to read data from its source but fails when the stream ends or errors (though it retries on interrupt).

Portability

Definitions

Given fixed inputs, all items (should) fall into one of three categories:

  • Output is non-deterministic, thus never reproducible
  • Output is deterministic, but not considered portable
  • Output is deterministic and portable

In general, functionality is considered deterministic and portable unless it is clearly non-deterministic (e.g. getrandom, ThreadRng) or it is documented as being unportable (e.g. StdRng, SmallRng).

Crate versions

We try to follow semver rules regarding API-breaking changes and MAJOR.MINOR.PATCH versions:

  • New patch versions should not include API-breaking changes or major new features
  • Before 1.0, minor versions may include API breaking changes. After 1.0 they should not.

Additionally, we must also consider value-breaking changes and portability. When given fixed inputs,

  • For non-deterministic items, implementations may change in any release
  • For deterministic unportable items, output should be preserved in patch releases, but may change in any minor release (including after 1.0)
  • For portable items, any change of output across versions is considered equivalent to an API breaking change.

Portability of usize

There is unfortunately one non-portable item baked into the heart of the Rust language: usize (and isize). For example, the size of an empty Vec will differ on 32-bit and 64-bit targets. For most purposes this is not an issue, but when it comes to generating random numbers in a portable manner it does matter.

A simple rule follows: if portability is required, never sample a usize or isize value directly.

Within Rand we adhere to this rule whenever possible. All sequence-releated code requiring a bounded usize value will sample a u32 value unless the upper bound exceeds u32::MAX. (Note that this actually improves benchmark performance in many cases.)

Updating

This guide is intended to facilitate upgrading to the next minor or major version of Rand. Note that updating to the next patch version (e.g. 0.5.1 to 0.5.2) should never require code changes.

This guide gives a few more details than the changelog, in particular giving guidance on how to use new features and migrate away from old ones.

Updating to 0.5

The 0.5 release has quite significant changes over the 0.4 release; as such, it may be worth reading through the following coverage of breaking changes. This release also contains many optimisations, which are not detailed below.

Crates

We have a new crate: rand_core! This crate houses some important traits, RngCore, BlockRngCore, SeedableRng and CryptoRng, the error types, as well as two modules with helpers for implementations: le and impls. It is recommended that implementations of generators use the rand_core crate while other users use only the rand crate, which re-exports most parts of rand_core.

The rand_derive crate has been deprecated due to very low usage and deprecation of Rand.

Features

Several new Cargo feature flags have been added:

  • alloc, used without std, allows use of Box and Vec
  • serde1 adds serialization support to some PRNGs
  • log adds logging in a few places (primarily to OsRng and JitterRng)

Rng and friends (core traits)

Rng trait has been split into two traits, a "back end" RngCore (implemented by generators) and a "front end" Rng implementing all the convenient extension methods.

Implementations of generators must impl RngCore instead. Usage of rand_core for implementations is encouraged; the rand_core::{le, impls} modules may prove useful.

Users of Rng who don't need to implement it won't need to make so many changes; often users can forget about RngCore and only import Rng. Instead of RngCore::next_u32() / next_u64() users should prefer Rng::gen(), and instead of RngCore::fill_bytes(dest), Rng::fill(dest) can be used.

Rng / RngCore methods

To allow error handling from fallible sources (e.g. OsRng), a new RngCore::try_fill_bytes method has been added; for example EntropyRng uses this mechanism to fall back to JitterRng if OsRng fails, and various handlers produce better error messages. As before, the other methods will panic on failure, but since these are usually used with algorithmic generators which are usually infallible, this is considered an appropriate compromise.

A few methods from the old Rng have been removed or deprecated:

  • next_f32 and next_f64; these are no longer implementable by generators; use gen instead
  • gen_iter; users may instead use standard iterators with closures: ::std::iter::repeat(()).map(|()| rng.gen())
  • gen_ascii_chars; use repeat as above and rng.sample(Alphanumeric)
  • gen_weighted_bool(n); use gen_bool(1.0 / n) instead

Rng has a few new methods:

  • sample(distr) is a shortcut for distr.sample(rng) for any Distribution
  • gen_bool(p) generates a boolean with probability p of being true
  • fill and try_fill, corresponding to fill_bytes and try_fill_bytes respectively (i.e. the only difference is error handling); these can fill and integer slice / array directly, and provide better performance than gen()

Constructing PRNGs

New randomly-initialised PRNGs

A new trait has been added: FromEntropy. This is automatically implemented for any type supporting SeedableRng, and provides construction from fresh, strong entropy:


# #![allow(unused_variables)]
#fn main() {
use rand::{ChaChaRng, FromEntropy};

let mut rng = ChaChaRng::from_entropy();
#}

Seeding PRNGs

The SeedableRng trait has been modified to include the seed type via an associated type (SeedableRng::Seed) instead of a template parameter (SeedableRng<Seed>). Additionally, all PRNGs now seed from a byte-array ([u8; N] for some fixed N). This allows generic handling of PRNG seeding which was not previously possible.

PRNGs are no longer constructed from other PRNGs via Rand support / gen(), but through SeedableRng::from_rng, which allows error handling and is intentionally explicit.

SeedableRng::reseed has been removed since it has no utility over from_seed and its performance advantage is questionable.

Implementations of SeedableRng may need to change their Seed type to a byte-array; this restriction has been made to ensure portable handling of Endianness. Helper functions are available in rand_core::le to read u32 and u64 values from byte arrays.

Block-based PRNGs

rand_core has a new helper trait, BlockRngCore, and implementation, BlockRng. These are for use by generators which generate a block of random data at a time instead of word-sized values. Using this trait and implementation has two advantages: optimised RngCore methods are provided, and the PRNG can be used with ReseedingRng with very low overhead.

Cryptographic RNGs

A new trait has been added: CryptoRng. This is purely a marker trait to indicate which generators should be suitable for cryptography, e.g. fn foo<R: Rng + CryptoRng>(rng: &mut R). Suitability for cryptographic use cannot be guaranteed.

Error handling

A new Error type has been added, designed explicitly for no-std compatibility, simplicity, and enough flexibility for our uses (carrying a cause when possible):


# #![allow(unused_variables)]
#fn main() {
pub struct Error {
    pub kind: ErrorKind,
    pub msg: &'static str,
    // some fields omitted
}
#}

The associated ErrorKind allows broad classification of errors into permanent, unexpected, transient and not-yet-ready kinds.

The following use the new error type:

  • RngCore::try_fill_bytes
  • Rng::try_fill
  • OsRng::new
  • JitterRng::new

External generators

We have a new generator, EntropyRng, which wraps OsRng and JitterRng (preferring to use the former, but falling back to the latter if necessary). This allows easy construction with fallback via SeedableRng::from_rng, e.g. IsaacRng::from_rng(EntropyRng::new())?. This is equivalent to using FromEntropy except for error handling.

It is recommended to use EntropyRng over OsRng to avoid errors on platforms with broken system generator, but it should be noted that the JitterRng fallback is very slow.

PRNGs

Pseudo-Random Number Generators (i.e. deterministic algorithmic generators) have had a few changes since 0.4, and are now housed in the prng module (old names remain temporarily available for compatibility; eventually these generators will likely be housed outside the rand crate).

All PRNGs now do not implement Copy to prevent accidental copying of the generator's state (and thus repetitions of generated values). Explicit cloning via Clone is still available. All PRNGs now have a custom implementation of Debug which does not print any internal state; this helps avoid accidentally leaking cryptographic generator state in log files. External PRNG implementations are advised to follow this pattern (see also doc on RngCore).

SmallRng has been added as a wrapper, currently around XorShiftRng (but likely another algorithm soon). This is for uses where small state and fast initialisation are important but cryptographic strength is not required. (Actual performance of generation varies by benchmark; depending on usage this may or may not be the fastest algorithm, but will always be fast.)

ReseedingRng

The ReseedingRng wrapper has been significantly altered to reduce overhead. Unfortunately the new ReseedingRng is not compatible with all RNGs, but only those using BlockRngCore.

ISAAC PRNGs

The IsaacRng and Isaac64Rng PRNGs now have an additional construction method: new_from_u64(seed). 64 bits of state is insufficient for cryptography but may be of use in simulations and games. This will likely be superseded by a method to construct any PRNG from any hashable object in the future.

HC-128

This is a new cryptographic generator, selected as one of the "stream ciphers suitable for widespread adoption" by eSTREAM. This is now the default cryptographic generator, used by StdRng and thread_rng().

Helper functions/traits

The Rand trait has been deprecated. Instead, users are encouraged to use Standard which is a real distribution and supports the same sampling as Rand. Rng::gen() now uses Standard and should work exactly as before. See the documentation of the distributions module on how to implement Distribution<T> for Standard for user types T

weak_rng() has been deprecated; use SmallRng::from_entropy() instead.

Distributions

The Sample and IndependentSample traits have been replaced by a single trait, Distribution. This is largely equivalent to IndependentSample, but with ind_sample replaced by just sample. Support for mutable distributions has been dropped; although it appears there may be a few genuine uses, these are not used widely enough to justify the existence of two independent traits or of having to provide mutable access to a distribution object. Both Sample and IndependentSample are still available, but deprecated; they will be removed in a future release.

Distribution::sample (as well as several other functions) can now be called directly on type-erased (unsized) RNGs.

RandSample has been removed (see Rand deprecation and new Standard distribution).

The Closed01 wrapper has been removed, but OpenClosed01 has been added.

Uniform distributions

Two new distributions are available:

  • Standard produces uniformly-distributed samples for many different types, and acts as a replacement for Rand
  • Alphanumeric samples chars from the ranges a-z A-Z 0-9

Ranges

The Range distribution has been heavily adapted, and renamed to Uniform:

  • Uniform::new(low, high) remains (half open [low, high))
  • Uniform::new_inclusive(low, high) has been added, including high in the sample range
  • Uniform::sample_single(low, high, rng) is a faster variant for single usage sampling from [low, high)

Uniform can now be implemented for user-defined types; see the uniform module.

Non-uniform distributions

Two distributions have been added:

  • Poisson, modeling the number of events expected from a constant-rate source within a fixed time interval (e.g. nuclear decay)
  • Binomial, modeling the outcome of a fixed number of yes-no trials

The sampling methods are based on those in "Numerical Recipes in C".

Exponential and Normal distributions

The main Exp and Normal distributions are unchanged, however the "standard" versions, Exp1 and StandardNormal are no longer wrapper types, but full distributions. Instead of writing let Exp1(x) = rng.gen(); you now write let x = rng.sample(Exp1);.

Updating to 0.6

During the 0.6 cycle, Rand found a new home under the rust-random project. We already feel at home, but if you'd like to help us decorate, a new logo would be appreciated!

We also found a new home for user-centric documentation — this book!

PRNGs

All PRNGs in our old PRNG module have been moved to new crates. We also added an additional crate with the PCG algorithms, and an external crate with Xoshiro / Xoroshiro algorithms:

SmallRng

This update, we switched the algorithm behind SmallRng from Xorshift to a PCG algorithm (either Pcg64Mcg aka XSL 128/64 MCG, or Pcg32 aka XSH RR 64/32 LCG aka the standard PCG algorithm).

Sequences

The seq module has been completely re-written, and the choose and shuffle methods have been removed from the Rng trait. Most functionality can now be found in the IteratorRandom and SliceRandom traits.

Weighted choices

The WeightedChoice distribution has now been replaced with WeightedIndex, solving a few issues by making the functionality more generic.

For convenience, the SliceRandom::choose_weighted method (and _mut variant) allow a WeightedIndex sample to be applied directly to a slice.

Other features

SIMD types

Rand now has rudimentary support for generating SIMD types, gated behind the simd_support feature flag.

i128 / u128 types

Since these types are now available on stable compilers, these types are supported automatically (with recent enough Rust version). The i128_support feature flag still exists to avoid breakage, but no longer does anything.

Updating to 0.7

Since the 0.6 release, rust-random gained a logo and a new crate: getrandom!

Dependencies

Rand crates now require rustc version 1.32.0 or later. This allowed us to remove all build.rs files for faster compilation.

The Rand crate now has fewer dependencies overall, though with some new ones.

Getrandom

As mentioned above, we have a new crate: getrandom, delivering a minimal API around platform-independent access to fresh entropy. This replaces the previous implementation in [OsRng], which is now merely a wrapper.

Core features

The FromEntropy trait has now been removed. Fear not though, its from_entropy method continues to provide easy initialisation from its new home in the SeedableRng trait (this requires that rand_core has the std or getrandom feature enabled):


# #![allow(unused_variables)]
#fn main() {
use rand::{SeedableRng, rngs::StdRng};
let mut rng = StdRng::from_entropy();
#}

The SeedableRng::from_rng method is now considered value-stable: implementations should have portable results.

The Error type of rand_core and rand has seen a major redesign; direct usage of this type is likely to need adjustment.

PRNGs

These have seen less change than in the previous release, but noteworthy is:

  • rand_chacha has been rewritten for much better performance (via SIMD instructions)
  • StdRng and ThreadRng now use the ChaCha algorithm. This is a value-breaking change for StdRng.
  • SmallRng is now gated behind the small_rng feature flag.
  • The xoshiro crate is now rand_xoshiro.
  • rand_pcg now includes Pcg64.

Distributions

For the most widely used distributions (Standard and Uniform), there have been no significant changes. But for most of the rest...

  • We added a new crate, [rand_distr], to house the all distributions (including re-exporting those still within rand::distributions). If you previously used rand::distributions::Normal, now you use rand_distr::Normal.
  • Constructors for many distributions changed in order to return a Result instead of panicking on error.
  • Many distributions are now generic over their parameter type (in most cases supporting f32 and f64). This aids usage with generic code, and allows reduced size of parameterised distributions. Currently the more complex algorithms always use f64 internally.
  • Standard can now sample NonZeroU* values

We also added several distributions:

Sequences

To aid portability, all random samples of type usize now instead sample a u32 value when the upper-bound is less than u32::MAX. This means that upgrading to 0.7 is a value-breaking change for use of seq functionality, but that after upgrading to 0.7 results should be consistent across CPU architectures.

Contributing

Thank you for your interest in contributing to Rand!

We are open to all contributors, but please consider that we have limited resources, usually have other on-going work within the project, and that even accepting complete PRs costs us time (review and potentially on-going support), thus we may take considerable time to get back to you.

All contributions

  • Scope: please consider whether your "issue" falls within the existing scope of the project or is an enhancement. Note that whether something is considered a defect may depend on your point of view. We may choose to reject contributions to avoid increasing our workload.

    If you wish to expand the scope of the project (e.g. new platforms or additional CI testing) then please be prepared to provide on-going support.

  • Fixes: if you can easily fix this yourself, please consider making a PR instead of opening an issue. On the other hand if it's less easy or looks like it may conflict with other work, don't hesistate to open an issue.

Pull Requests

  • Changelog: unless your change is trivial, please include a note in the changelog (CHANGELOG.md) of each crate affected, under the [Unreleased] heading at the top (add if necessary). Please include the PR number (this implies the note must be added after opening a PR).

  • Commits: if contributing large changes, consider splitting these over multiple commits, if possible such that each commit at least compiles. Rebasing commits may be appropriate when making significant changes.

  • Documentation: we require documentation of all public items. Short examples may be included where appropriate.

  • Maintainability: it is important to us that code is easy to read and understand and not hard to review for correctness.

  • Performance: we always aim for good performance and sometimes do considerable extra work to get there, however we must also make compromises for the sake of maintainability, and consider whether a minor efficiency gain is worth the extra code complexity. Use benchmarks.

  • Style: make it neat. Usually limit length to 80 chars.

  • Unsafe: use it where necessary, not if there is a good alternative. Ensure unsafe code is easy to review for correctness.

  • License and attribution: this project is freely licenced under the MIT and Apache Public Licence v2. We assume that all contributions are made under these licence grants. Copyrights are retained by their contributors.

    Our works are attributed to "The Rand Project Developers". This is not a formal entity but merely the collection of all contributors to this project. For more, see the COPYRIGHT file.

  • Thank you!

Documentation

Style

All documentation is in English, but no particular dialect is preferred.

The documentation should be accessible to multiple audiences: both seasoned Rustaceans and relative newcomers, those with experience in statistical modelling or cryptography, as well as those new to the subjects. Since it is often impossible to write appropriate one-size-fits-all documentation, we prefer concise technical documentation with reference to extended articles aimed at more specific audiences.

API documentation

Rand crates

It is recommended to use nightly Rust for correct link handling.

To build all API documentation for all crates in the rust-random/rand repository, run:

# Build doc for all modules:
cargo doc --all --no-deps

# And open it:
xdg-open target/doc/rand/index.html

On Linux, it is easy to set up automatic rebuilds after any edit:

while inotifywait -r -e close_write src/ rand_*/; do cargo doc; done

After editing API documentation, we reccomend testing examples and checking for broken links:

cargo test --doc

cargo install cargo-deadlinks
# It is recommended to remove left-over files from previous compilations
rm -rf /target/doc
cargo doc --all --no-deps
cargo deadlinks --dir target/doc

Rand API docs are automatically built and hosted at rust-random.github.io/rand for the latest code in master.

Getrandom crate

The rust-random/getrandom repository contains only a single crate, hence a simple cargo doc will suffice.

Cross-crate links

When referring to another crate, we prefer linking to the crate page on crates.io since (a) this includes the README documenting the purpose of the crate and (b) this links directly to both the repository and the API documentation. Example:


# #![allow(unused_variables)]
#fn main() {
// Link to the crate page:
//! [`rand_chacha`]: https://crates.io/crates/rand_chacha
#}

When referring to an item from within another crate,

  1. if that item is accessible via a crate dependency (even if not via the public API), use the Rust item path
  2. when linking to another crate within the rust-random/rand repository, relative paths within the generated documentation files (under target/doc) can be used; these work on rust-random.github.io/rand but not currently on docs.rs (see docs#204)
  3. if neither of the above are applicable, use an absolute link
  4. consider revising documentation, e.g. refer to the crate instead

Examples:

// We depend on rand_core, therefore can use the Rust path:
/// [`BlockRngCore`]: rand_core::block::BlockRngCore

// rand_chacha is not a dependency, but is within the same repository:
//! [`ChaCha20Rng`]: ../../rand_chacha/struct.ChaCha20Rng.html

// Link directly to docs.rs, with major & minor but no patch version:
https://docs.rs/getrandom/0.1/getrandom/fn.getrandom.html

Auxilliary documentation

README files

README files contain a brief introduction to the crate, shield badges, useful links, feature-flag documentation, licence information, and potentially an example.

For the most part these files do not have any continuous testing. Where examples are included (currently only for the rand_jitter crate), we enable continuous testing via doc_comment (see lib.rs:62 onwards).

CHANGELOG files

Changelog formats are based on the Keep a Changelog format.

All significant changes merged since the last release should be listed under an [Unreleased] section at the top of log.

The book

The source to this book is contained in the rust-random/book repository. It is built using mdbook, which makes building and testing easy:

cargo install mdbook --version "^0.2"

mdbook build --open
mdbook test

# To automatically rebuild after any changes:
mdbook watch

Note that links in the book are relative and designed to work in the published book. If you build the book locally, you might want to set up a symbolic link pointing to your build of the API documentation:

ln -s ../rand/target/doc rand

Scope

Over time, the scope of the project has grown, and Rand has moved from using a monolithic crate to using a "main" crate plus multiple single-purpose crates. For new functionality, one must consider where, and whether, it fits within the Rand project.

Small, focussed crates may be used for a few reasons, but we aim not to maximally divide functionality into small crates. Valid reasons for using a separate crate for a feature are therefore:

  • to allow a clear dependency hierarchy (rand_core)
  • to make the feature available in a stand-alone fashion (e.g. getrandom)
  • to remove little-used features with non-trivial amounts of code from widely used crates (e.g. rand_jitter and rand_distr both extracted functionality from rand)
  • to allow choice, without including large amounts of unused code for all users, but also without producing an enormous number of new crates (RNG family crates like rand_xoshiro and rand_isaac)

Traits, basics and UI

The main user interface to the Rand project remains the central rand crate. Goals for this crate are:

  • ease of use
  • expose commonly used functionality in a single place
  • permit usage of additional randomness sources and distribution samplers

To allow better modularity, the core traits have been moved to the rand_core crate. Goals of this crate are:

  • expose the core traits with minimal dependencies
  • provide common tools needed to implement various randomness sources

External random sources

The main (and usually only) external source of randomness is the Operating System, interfaced via the getrandom crate. This crate also supports usage of RDRAND on a few no_std targets.

Support for other no_std targets has been discussed but with little real implementation effort. See getrandom#4.

The rand_jitter crate provides an implementation of a CPU Jitter entropy harvestor, and is only included in Rand for historical reasons.

The rand_os crate proves an RngCore implementation around getrandom.

Pseudo-random generators

The Rand library includes several pseudo-random number generators, for the following reasons:

  • to implement the StdRng and SmallRng generators
  • to provide a few high-quality alternative generators
  • historical usage

These are implemented within "family" crates, e.g. rand_chacha, rand_pcg, rand_xoshiro.

We have received several requests to adopt new algorithms into the library; when evaluating such requests we must consider several things:

  • purpose for inclusion within Rand
  • whether the PRNG is cryptographically secure, and if so, how trustworthy such claims are
  • statistical quality of output
  • performance and features of the generator
  • reception and third-party review of the algorithm

Distributions

The Distribution trait is provided by Rand, along with commonly-used distributions (mostly linear ones).

Additional distributions are packaged within the rand_distr crate, which depends on rand and re-exports all of its distributions.

Testing

Rand has a number of unit tests, though these are not comprehensive or perfect (improvements welcome). We prefer to have tests for all new functionality.

The first line of testing is simply to run cargo test from the appropriate directory. Since Rand supports no_std (core-only), core+alloc and std environments, it is important to test all three (depending on which features are applicable to the code in question):

# Test using std:
cargo test
# Test using only core:
cargo test --tests --no-default-features
# Test using core + alloc (requires nightly):
cargo +nightly test --tests --no-default-features --features=alloc

It may also be worth testing with other feature flags:

cargo test --all-features

Note that this only tests the current package (i.e. the main Rand lib when run from the repo's top level). To test another lib, cd to its directory.

We do not recommend using Cargo's --package option due to its surprising interactions with --feature options and failure when multiple versions of the same package are in the build tree. The CI instead uses --manifest-path to select packages; while developing, using cd is easier.

Writing tests

Tests may be unit tests within a test sub-module, documentation examples, example applications (examples dir), integration tests (tests dir), or benchmarks (benches dir).

Note that only unit tests and integration tests are expected to pass in no_std (core only) and core+alloc configurations. This is a deliberate choice; example code should only need to target the common case (std).

Random Number Generators

Often test code needs some RNG to test with, but does not need any particular RNG. In this case, we prefer use of ::test::rng which is simple, fast to initialise and deterministic:


# #![allow(unused_variables)]
#fn main() {
let mut rng = ::test::rng(528); // just pick some number
#}

Various tests concern properties which are probably true, but not definitely. We prefer that such tests are deterministic to avoid spurious failures.

Benchmarks

We already have many benchmarks:

cargo +nightly bench

# In a few cases, nightly features may use different code paths:
cargo +nightly bench --features=nightly

Benchmarks for distributions now live in the rand_distr crate; all other benchmarks (including all our RNGs) live in the main rand crate (hence the many dev-dependencies).

A lot of code in Rand is performance sensitive, most of it is expected to be used in hot loops in some libraries/applications. If you change code in rand_core, in PRNG crates, or in the rngs or distributions modules (especially when an 'obvious cleanup'), make sure the benchmarks do not regress.

Please report before-and-after results for any affected benchmarks. If you are optimising something previously not benchmarked, please add new benchmarks first, then add your changes in a separate commit (to make before-and-after benchmarking easy).