Eze Sunday Eze Sunday is a full-stack software developer and technical writer passionate about solving problems, one line of code at a time. Contact Eze at [email protected]

Comparing random number generators in Rust

5 min read 1673

Rust Logo

Randomness is an invaluable part of both web development and our lives today. Without randomness, Bitcoin and cryptocurrencies, the banking system, a game of chance like dice or roulette wheel, and, of course, all cryptographic operations, would be predictable and insecure.

In Rust, there are several random number generator implementation crates. Below, we’ll look at the popular random number generators, compare and contrast them, and explore their advantages and disadvantages and their use cases.

Note: Unlike JavaScript, there is no Math.random method equivalent currently available in Rust.

Using Rand, a popular random number generator

Rand is the most popular random number generator crate in the Rust ecosystem and is maintained by rust-random and 229 contributors. It has a GitHub rating of over 1,090 stars. Rand offers many features that you can use for different cases of randomness within Rust, and it currently uses the ChaCha block cipher algorithm with 12 rounds, which is fast, statistically strong, and unpredictable so that it can adequately generate cryptographically secure pseudorandom numbers (CSPRNG).

Rand has a small file size of 87.1KB and a significant amount of downloads. As of the time of this writing, it has been downloaded over 116,114,195 times.

Top features of Rand

1. Deterministic random number generator

Deterministic random numbers are generated from a seed — a set of defined numbers. Anyone with that seed can re-generate the same number. Take the piece of code below, for example:

use rand_chacha;

fn generate_random_numbers_with_a_seed(seed : u64) {
    let mut rng = rand_chacha::ChaCha8Rng::seed_from_u64(seed);
    println!("Deterministic Random numbers with seed:{}", rng.gen::<i32>());
}

fn main(){
generate_random_numbers_with_a_seed(200); 
//Result : -416273517
}

The result will always be -416273517 as long as you are using the seed 200. To make this example work, you’ll need to add rand_chacha as a dependency to your Cargo.toml file, like so:

[dependencies]
rand_chacha = "0.3.1"

In previous Rand versions, you only needed to install Rand to use rand_chacha because it was a dependency. But, as of August 2019, rand_chacha is separate because the team was looking to reduce dependencies.

Rand GitHub Issue Conversation

2. Non-deterministic random number generator

Rand also allows you to create random numbers without a seed because it’s seeded by the system and re-seeded only when necessary. You can trigger the re-seeding process manually, like this:



use rand::rngs::OsRng;
use rand::rngs::adapter::ReseedingRng;
use rand::prelude::*;
use rand_chacha::ChaCha20Core; 

fn main(){
    let prng = ChaCha20Core::from_entropy();
    let mut reseeding_rng = ReseedingRng::new(prng, 0, OsRng); //Reseeding
    println!("Random number: {}", reseeding_rng.gen::<u64>());
}

Re-seeding improves your security in case of a side-channel attack and/or misuse. Essentially, re-seeding means you generate a new seed from an entropy generated by your system.

However, in most cases, you might use the thread-local generator thread_rng() more often to generate random numbers with Rand. This is because it’s usually sufficient for generating the randomness you need (and is seeded by your system). The key is that you must use a new seed for every random number. Here’s an example using the thread-local generator in Rand.

let mut rng = rand::thread_rng();
println!("{}", rng.gen::<i32>()); //Result is an unknown value
println!("{}", rng.gen::<i64>()); //Result is an unknown value
println!("{}", rng.gen::<u32>()); //Result is an unknown value
println!("{}", rng.gen::<u64>()); //Result is an unknown value

There are several functions within the Rand crate — check Rand’s documentation to learn more.

So, what are the ups and downsides to using Rand within your Rust application? Mostly, Rand has almost everything you need to work with secured randomness, which is great. If, however, you are looking to implement basic randomness without worrying too much about complexity or security, Rand might be overkill for your project.

Using Rand in Rust

Let’s cover some use cases for Rand below. With Rand, you can:

  1. Generate random numbers from a range of numbers .gen_range(1.0..100.0):
    let mut rng = thread_rng();
    let random_number = rng.gen_range(1.0..100.0);
    println!("Random from range {}", random_number);
  2. Generate a random number by shuffling an array:
    let mut rng = rand::thread_rng();
    let mut arr = (1..100).collect::<Vec<i32>>();
    arr.shuffle(&mut rng);
    println!("Shuffle array: {:?}", arr);
  3. Generate a random number from a seed:
    let mut rng = rand_chacha::ChaCha8Rng::seed_from_u64(1000);
    println!("Deterministic Random numbers with seed:{}", rng.gen::<i32>()); // Ghan
  4. Flip a coin:
    fn flip_coin() {
        let mut rng = rand::thread_rng();
        if rng.gen::<bool>() {
            println!("heads");
        } else {
            println!("tails");
        }
    }

There is so much more you can do with Rand, but you should check the docs to learn more.

Using Fastrand with Rust

Fastrand is a small and fast random number generator based on WyRand, which is a modern non-cryptographic hash function and pseudo-random number generator. It was released two years ago, and, unlike Rand, Fastrand is not cryptographically secure, so it shouldn’t be used for that purpose. It also only has one dependency, Instant, whereas Rand has six.

Features of Fastrand

Fastrand’s random numbers are only generated based on a predetermined seed so that anyone with that seed can generate the same number.

Pros and cons of using Fastrand

Fastrand is simple and compact. Most importantly, it’s fast. However, Fastrand is not cryptographically secure. so if you are looking for something more secure to use for cryptography, use Rand instead.


More great articles from LogRocket:


Community Support

At the time of this writing, there are no Stack Overflow questions on using Fastrand, and there are only a few issues on GitHub. The project has over eight contributors and 10,976,949 downloads at the moment. When compared to other random number generators, the community around Fastrand is growing fast, but not as fast as Rand.

Using Fastrand in Rust

Let’s see some examples of what you can do with Fastrand.

  1. First, you can generate a random boolean and true or false value, which will be good for a coin-flipping scenario.
    // Flip a coin:
    if fastrand::bool() {
          println!("Fast Rand: heads");
        } else {
          println!("Fast Rand: tails");
    }
  2. With Fastrand, you can generate random numbers based on types, as well as signed and unsigned numbers:
    println!("Fast Rand: Unsigned Random number 32bit: {}", fastrand::u32(..));
        println!("Fast Rand: Unsigned Random unsigned number 64bit: {}", fastrand::u64(..));
        println!("Fast Rand: Signed Random number 64bit: {}", fastrand::i64(..));
    }
    
  3. You can also pick a random number from a collection, like so:
    let i = fastrand::usize(vec![1,3,5,6,7,8]);
    let element = arr[i];
    println!("Fast Rand: Random number from array {:?}: is {}", arr, elem);
  4. You can generate random numbers from an array by shuffling it:
    fn shuffle(mut arr: Vec<u32>) {
        let shuffled = fastrand::shuffle(&mut arr);
        println!("Random numbers from Shuffled array {:?}", shuffled);
    }

You should check the documentation for more use cases.

Using Nanorand for randomness in Rust

Nanorand is an interesting random number generator that is simple, even though it allows you to use over one algorithm. You can choose which algorithm you want to use depending on your needs.

Features of Nanorand

First and foremost, Nanorand allows you to use multiple algorithms, such as WyRand, ChaCha, and Pcg64.

Algorithms for Nanorand

So, whether you are looking to generate a cryptographically secure PRNG or a non-secure random number with a quick compile time, you can get it done using Nanorand.

Pros and cons of using Nanorand

Nanorand is lightweight, about 18.4KB, and fast, depending on the type of random numbers you are trying to generate. Remember: cryptographically secure random numbers will be much slower to generate than non-cryptographically secure random numbers.

Currently, Nanorand does not have much community support, and the documentation does not cover other algorithms it implements apart from WyRand. However, Nanorand has been installed over 834,185 times, has 11 contributors, and has few issues on GitHub.

Use cases

Nanorand’s use cases are like other random numbers generators we’ve discussed already.

  1. Generate random numbers based on types, such as u64, u32, i32, etc.
    let mut rng = WyRand::new();
    println!("Nano Rand: random number 64bit: {}", rng.generate::<u64>());
    println!("Nano Rand: random number 32bit: {}", rng.generate::<u32>());
  2. Generate random numbers from an array by shuffling the array:
    let mut rng = WyRand::new();
    let mut items = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
    rng.shuffle(&mut items);
    println!("Nano: Shuffled array vec {:?}", items);
  3. Generate random numbers between a range of numbers.
    let mut rng = WyRand::new();
    println!(
        "Random number between 1 and 100: {}",
        rng.generate_range(1_u64..=100)
    );

Using oorandom in Rust

Oorandom is a deterministic random number generator that implements permuted congruential generator (PCG). Use oorandom if you find the rand crate to be too large for your project and just want something more compact and specific. More so, it’s #[no_std], which is great if you are building a project for bare-metal devices.

To generate a random number, you will need to generate it from a seed. If you want to generate a non-deterministic random number, use the getrandom crate to generate random seeds from your system.

Pros and cons of using oorandom

  • Its file size is around 10.1KB and it has no dependencies
  • It uses PCG, which offers excellent statistical performance

Oorandom is, unfortunately, not cryptographically secure.

Community

Oorandom is quite popular, as it’s been installed over 8,107,644 times. For most people who need a simple random number generator, oorandom is a great option.

Use cases

Oorandom implements just a handful of functions: rand_float, rand_i32, rand_range, and rand_u32.

Oorand Functions

Here is an example of how you can use it:

use oorandom;

fn main(){
  let seed = 4;
  let mut rng = oorandom::Rand32::new(seed);
  println!("OORandom: Random number 32bit: {}", rng.rand_i32());
  println!("OORandom: Random number range: {}", rng.rand_range(1..100));
  println!("OORandom: Random number with float: {}", rng.rand_float());
}

Conclusion

With Rust, there is so much we can do by using random number generators. While the generators on this list may change over time as stronger algorithms are created, we discussed some of the go-to options for devs working with Rust, security, and/or cryptography in this article. Happy hacking!

LogRocket: Full visibility into production Rust apps

Debugging Rust applications can be difficult, especially when users experience issues that are difficult to reproduce. If you’re interested in monitoring and tracking performance of your Rust apps, automatically surfacing errors, and tracking slow network requests and load time, try LogRocket.

LogRocket is like a DVR for web and mobile apps, recording literally everything that happens on your Rust app. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred. LogRocket also monitors your app’s performance, reporting metrics like client CPU load, client memory usage, and more.

Modernize how you debug your Rust apps — .

Eze Sunday Eze Sunday is a full-stack software developer and technical writer passionate about solving problems, one line of code at a time. Contact Eze at [email protected]

Leave a Reply