How do I work with Rand for Random Number Generation in Rust?

Walkthrough

Rand is the de facto standard library for random number generation in Rust. It provides a unified interface for various random number generators (RNGs), distributions, and utilities for generating random values. Rand supports both cryptographically secure and non-secure random number generation, making it suitable for games, simulations, cryptography, and more.

Key concepts:

  • RNG (Random Number Generator) — The core trait for generating random bytes
  • ThreadRng — Thread-local RNG (fast, cryptographically secure)
  • StdRng — Standard RNG with reproducibility
  • Distributions — Different probability distributions (Uniform, Normal, etc.)
  • Seeding — Controlling RNG initialization for reproducibility

When to use Rand:

  • Games and simulations
  • Generating random data for testing
  • Password and token generation
  • Sampling from datasets
  • Cryptographic applications (with appropriate RNG)

When NOT to use Rand:

  • Deterministic algorithms (unless seeded)
  • Very performance-critical inner loops (consider optimized solutions)

Code Examples

Basic Random Numbers

use rand::Rng;
 
fn main() {
    let mut rng = rand::thread_rng();
    
    // Random integer in range [0, 100)
    let n: i32 = rng.gen_range(0..100);
    println!("Random number: {}", n);
    
    // Random float in range [0.0, 1.0)
    let f: f64 = rng.gen();
    println!("Random float: {}", f);
    
    // Random boolean
    let b: bool = rng.gen();
    println!("Random bool: {}", b);
}

Random Numbers in Ranges

use rand::Rng;
 
fn main() {
    let mut rng = rand::thread_rng();
    
    // Exclusive range [1, 10)
    let n1: i32 = rng.gen_range(1..10);
    println!("1-9: {}", n1);
    
    // Inclusive range [1, 10]
    let n2: i32 = rng.gen_range(1..=10);
    println!("1-10: {}", n2);
    
    // Float range
    let f: f64 = rng.gen_range(0.0..1.0);
    println!("Float: {}", f);
    
    // Character range
    let c: char = rng.gen_range('a'..='z');
    println!("Letter: {}", c);
}

Random Element from Collection

use rand::seq::SliceRandom;
use rand::thread_rng;
 
fn main() {
    let mut rng = thread_rng();
    
    let choices = vec!["apple", "banana", "cherry", "date", "elderberry"];
    
    // Choose a random element
    if let Some(fruit) = choices.choose(&mut rng) {
        println!("Random fruit: {}", fruit);
    }
    
    // Choose multiple random elements
    let sampled: Vec<_> = choices.choose_multiple(&mut rng, 3).collect();
    println!("Sampled: {:?}", sampled);
    
    // Shuffle the collection
    let mut numbers: Vec<i32> = (1..=10).collect();
    numbers.shuffle(&mut rng);
    println!("Shuffled: {:?}", numbers);
}

Random String Generation

use rand::Rng;
use rand::distributions::{Alphanumeric, DistString};
 
fn main() {
    let mut rng = rand::thread_rng();
    
    // Random alphanumeric string
    let s: String = Alphanumeric.sample_string(&mut rng, 16);
    println!("Random string: {}", s);
    
    // Random string from custom characters
    const CHARSET: &[u8] = b"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
    
    let password: String = (0..20)
        .map(|_| {
            let idx = rng.gen_range(0..CHARSET.len());
            CHARSET[idx] as char
        })
        .collect();
    println!("Password: {}", password);
}

Seeding for Reproducibility

use rand::{Rng, SeedableRng};
use rand::rngs::StdRng;
 
fn main() {
    // Create RNG with fixed seed for reproducibility
    let seed: u64 = 42;
    let mut rng1 = StdRng::seed_from_u64(seed);
    let mut rng2 = StdRng::seed_from_u64(seed);
    
    // Both will produce the same sequence
    let n1: i32 = rng1.gen_range(0..100);
    let n2: i32 = rng2.gen_range(0..100);
    
    println!("rng1: {}, rng2: {}", n1, n2);
    assert_eq!(n1, n2);  // Always true
}

Random Tuples and Arrays

use rand::Rng;
 
fn main() {
    let mut rng = rand::thread_rng();
    
    // Random tuple
    let point: (f64, f64) = rng.gen();
    println!("Random point: ({}, {})", point.0, point.1);
    
    // Random array
    let arr: [u8; 8] = rng.gen();
    println!("Random bytes: {:02x?}", arr);
    
    // Fill existing array
    let mut buffer = [0u8; 32];
    rng.fill(&mut buffer);
    println!("Buffer: {:02x?}", buffer);
}

Probability Distributions

use rand::distributions::{Distribution, Uniform, Normal, Alphanumeric};
use rand::thread_rng;
 
fn main() {
    let mut rng = thread_rng();
    
    // Uniform distribution (default for gen_range)
    let uniform = Uniform::from(0..100);
    let n: i32 = uniform.sample(&mut rng);
    println!("Uniform: {}", n);
    
    // Normal (Gaussian) distribution
    let normal = Normal::new(0.0, 1.0).unwrap();  // mean=0, std_dev=1
    let gaussian: f64 = normal.sample(&mut rng);
    println!("Gaussian: {}", gaussian);
    
    // Sample multiple values
    let values: Vec<f64> = normal.sample_iter(&mut rng).take(5).collect();
    println!("Multiple: {:?}", values);
}

Weighted Random Selection

use rand::seq::IteratorRandom;
use rand::thread_rng;
 
fn main() {
    let mut rng = thread_rng();
    
    let items = vec![('a', 1), ('b', 2), ('c', 3), ('d', 4)];
    
    // Weighted selection (simplified approach)
    let total_weight: i32 = items.iter().map(|(_, w)| w).sum();
    let choice = rng.gen_range(1..=total_weight);
    
    let mut cumulative = 0;
    for (item, weight) in items {
        cumulative += weight;
        if choice <= cumulative {
            println!("Selected: {}", item);
            break;
        }
    }
}

Bernoulli Distribution

use rand::distributions::{Distribution, Bernoulli};
use rand::thread_rng;
 
fn main() {
    let mut rng = thread_rng();
    
    // 70% chance of success
    let bernoulli = Bernoulli::new(0.7).unwrap();
    
    let mut successes = 0;
    for _ in 0..100 {
        if bernoulli.sample(&mut rng) {
            successes += 1;
        }
    }
    
    println!("Successes: {}/100", successes);
}

Cryptographically Secure RNG

use rand::rngs::OsRng;
use rand::RngCore;
 
fn main() {
    let mut rng = OsRng;
    
    // Cryptographically secure random bytes
    let mut key = [0u8; 32];
    rng.fill_bytes(&mut key);
    
    println!("Secure key: {:02x?}", key);
    
    // Random u64
    let secure_u64 = rng.next_u64();
    println!("Secure u64: {}", secure_u64);
}

UUID Generation

use rand::Rng;
 
fn generate_uuid_v4() -> String {
    let mut rng = rand::thread_rng();
    
    let mut bytes = [0u8; 16];
    rng.fill(&mut bytes);
    
    // Set version to 4 (random)
    bytes[6] = (bytes[6] & 0x0f) | 0x40;
    // Set variant to RFC4122
    bytes[8] = (bytes[8] & 0x3f) | 0x80;
    
    format!(
        "{:02x}{:02x}{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}",
        bytes[0], bytes[1], bytes[2], bytes[3],
        bytes[4], bytes[5],
        bytes[6], bytes[7],
        bytes[8], bytes[9],
        bytes[10], bytes[11], bytes[12], bytes[13], bytes[14], bytes[15]
    )
}
 
fn main() {
    println!("UUID: {}", generate_uuid_v4());
}

Custom Random Type

use rand::Rng;
use rand::distributions::{Distribution, Standard};
 
#[derive(Debug)]
enum Coin {
    Heads,
    Tails,
}
 
impl Distribution<Coin> for Standard {
    fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> Coin {
        if rng.gen_bool(0.5) {
            Coin::Heads
        } else {
            Coin::Tails
        }
    }
}
 
fn main() {
    let mut rng = rand::thread_rng();
    
    // Now we can use gen() for Coin
    let flip: Coin = rng.gen();
    println!("Flipped: {:?}", flip);
    
    // Multiple flips
    let flips: Vec<Coin> = (0..5).map(|_| rng.gen()).collect();
    println!("Flips: {:?}", flips);
}

Shuffling and Permutations

use rand::seq::SliceRandom;
use rand::thread_rng;
 
fn main() {
    let mut rng = thread_rng();
    
    // Full shuffle
    let mut deck: Vec<&str> = vec![
        "A♠", "2♠", "3♠", "4♠", "5♠", "6♠", "7♠", "8♠", "9♠", "10♠", "J♠", "Q♠", "K♠",
        "A♥", "2♥", "3♥", "4♥", "5♥", "6♥", "7♥", "8♥", "9♥", "10♥", "J♥", "Q♥", "K♥",
    ];
    
    deck.shuffle(&mut rng);
    println!("First 5 cards: {:?}", &deck[..5]);
    
    // Partial shuffle
    let mut numbers: Vec<i32> = (1..=100).collect();
    numbers.partial_shuffle(&mut rng, 5);
    println!("First 5 after partial shuffle: {:?}", &numbers[..5]);
}

Random Sampling Without Replacement

use rand::seq::IteratorRandom;
use rand::thread_rng;
 
fn main() {
    let mut rng = thread_rng();
    
    // Sample from iterator without replacement
    let sample: Vec<i32> = (1..=1000)
        .choose_multiple(&mut rng, 5);
    
    println!("Sampled 5 from 1000: {:?}", sample);
    
    // Sample from array
    let items = vec!["a", "b", "c", "d", "e", "f", "g", "h"];
    let chosen: Vec<_> = items.iter().choose_multiple(&mut rng, 3);
    println!("Chosen 3: {:?}", chosen);
}

Random Index Generation

use rand::seq::index;
use rand::thread_rng;
 
fn main() {
    let mut rng = thread_rng();
    
    // Get random indices from a range
    let indices: Vec<usize> = index::sample(&mut rng, 100, 5).into_iter().collect();
    println!("Random indices from 0-99: {:?}", indices);
    
    // Get all indices in random order
    let shuffled: Vec<usize> = index::shuffle(&mut rng, 10).into_vec();
    println!("Shuffled 0-9: {:?}", shuffled);
}

Dice Rolling Simulation

use rand::Rng;
use rand::distributions::{Distribution, Uniform};
 
fn roll_die(sides: u32) -> u32 {
    let mut rng = rand::thread_rng();
    rng.gen_range(1..=sides)
}
 
fn roll_dice(count: u32, sides: u32) -> Vec<u32> {
    let mut rng = rand::thread_rng();
    let die = Uniform::from(1..=sides);
    (0..count).map(|_| die.sample(&mut rng)).collect()
}
 
fn main() {
    let mut rng = rand::thread_rng();
    
    // Roll a d20
    let d20 = roll_die(20);
    println!("d20: {}", d20);
    
    // Roll 2d6
    let two_d6 = roll_dice(2, 6);
    let sum: u32 = two_d6.iter().sum();
    println!("2d6: {:?} = {}", two_d6, sum);
    
    // Roll 4d6, drop lowest (D&D stat generation)
    let mut rolls = roll_dice(4, 6);
    rolls.sort();
    let stat: u32 = rolls[1..].iter().sum();
    println!("4d6 drop lowest: {:?} = {}", rolls, stat);
}

Testing with Deterministic RNG

use rand::{Rng, SeedableRng};
use rand::rngs::StdRng;
 
fn game_simulation(rng: &mut StdRng) -> i32 {
    // Simulate game with random elements
    let score: i32 = rng.gen_range(0..100);
    score
}
 
#[cfg(test)]
mod tests {
    use super::*;
    
    #[test]
    fn test_deterministic() {
        let mut rng1 = StdRng::seed_from_u64(42);
        let mut rng2 = StdRng::seed_from_u64(42);
        
        // Same seed = same results
        assert_eq!(rng1.gen::<u32>(), rng2.gen::<u32>());
    }
    
    #[test]
    fn test_game_simulation() {
        let mut rng = StdRng::seed_from_u64(12345);
        let score = game_simulation(&mut rng);
        
        // Deterministic score for testing
        assert!(score >= 0 && score < 100);
    }
}
 
fn main() {
    let mut rng = StdRng::seed_from_u64(42);
    let score = game_simulation(&mut rng);
    println!("Score: {}", score);
}

Performance: Thread-local vs StdRng

use rand::{Rng, SeedableRng};
use rand::rngs::StdRng;
use std::time::Instant;
 
fn benchmark_thread_rng(count: u32) -> u32 {
    let mut rng = rand::thread_rng();
    let mut sum = 0u32;
    for _ in 0..count {
        sum = sum.wrapping_add(rng.gen_range(0..1000));
    }
    sum
}
 
fn benchmark_std_rng(count: u32) -> u32 {
    let mut rng = StdRng::from_entropy();
    let mut sum = 0u32;
    for _ in 0..count {
        sum = sum.wrapping_add(rng.gen_range(0..1000));
    }
    sum
}
 
fn main() {
    let iterations = 1_000_000;
    
    let start = Instant::now();
    benchmark_thread_rng(iterations);
    let thread_duration = start.elapsed();
    
    let start = Instant::now();
    benchmark_std_rng(iterations);
    let std_duration = start.elapsed();
    
    println!("ThreadRng: {:?}", thread_duration);
    println!("StdRng: {:?}", std_duration);
}

Hex String Generation

use rand::Rng;
 
fn random_hex(length: usize) -> String {
    let mut rng = rand::thread_rng();
    
    (0..length)
        .map(|_| format!("{:02x}", rng.gen::<u8>()))
        .collect()
}
 
fn main() {
    println!("Hex: {}", random_hex(16));
}

Summary

Rand Key Imports:

use rand::Rng;
use rand::thread_rng;
use rand::seq::SliceRandom;
use rand::distributions::{Distribution, Uniform, Alphanumeric};

RNG Types:

Type Description
thread_rng() Thread-local, secure, convenient
StdRng Reproducible, seedable
OsRng Cryptographically secure from OS

Core Methods:

Method Description
gen() Generate random value
gen_range(a..b) Random in range
gen_bool(p) Boolean with probability
fill(&mut buf) Fill buffer with random bytes

Distributions:

Distribution Description
Uniform Equal probability in range
Normal Gaussian distribution
Bernoulli Boolean with probability
Alphanumeric Random alphanumeric chars

Sequence Operations:

Method Description
choose() Random element
choose_multiple() Multiple random elements
shuffle() Shuffle in place
partial_shuffle() Partial shuffle

Seeding:

// From entropy (random seed)
let rng = StdRng::from_entropy();
 
// From specific seed (reproducible)
let rng = StdRng::seed_from_u64(42);

Key Points:

  • Use thread_rng() for most cases (fast, secure)
  • Use StdRng with seed for reproducibility
  • Use OsRng for cryptographic security
  • Distributions provide probability patterns
  • SliceRandom for collection operations
  • Alphanumeric for random strings
  • Seed for deterministic testing
  • gen_range(1..=10) for inclusive range