How do I generate random numbers in Rust?

Walkthrough

Rand is the standard random number generation library for Rust. It provides thread-local random number generators, various distributions (uniform, normal, exponential), cryptographically secure random generation, and utilities for shuffling and sampling. Rand uses the ChaCha algorithm by default for reproducibility across platforms.

Key concepts:

  1. rand::Rng trait — core interface for generating random values
  2. random() function — quick one-off random values
  3. thread_rng() — thread-local generator (most common)
  4. Distributions — uniform, normal, weighted, and custom distributions
  5. Seeded generators — reproducible random sequences

Rand is essential for simulations, games, sampling, testing, and cryptography.

Code Example

# Cargo.toml
[dependencies]
rand = "0.8"
use rand::Rng;
 
fn main() {
    // Quick random value
    let n: i32 = rand::random();
    println!("Random i32: {}", n);
    
    // Random boolean
    let b: bool = rand::random();
    println!("Random bool: {}", b);
    
    // Thread-local generator (preferred)
    let mut rng = rand::thread_rng();
    
    // Random integer in range [1, 100)
    let dice_roll: i32 = rng.gen_range(1..=6);
    println!("Dice roll: {}", dice_roll);
    
    // Random float in range [0.0, 1.0)
    let probability: f64 = rng.gen::<f64>();
    println!("Random f64: {}", probability);
    
    // Random float in custom range
    let percent: f64 = rng.gen_range(0.0..100.0);
    println!("Random percentage: {:.2}%", percent);
}

Integer and Float Ranges

use rand::Rng;
 
fn main() {
    let mut rng = rand::thread_rng();
    
    // Integer ranges
    let small: i32 = rng.gen_range(1..10);        // [1, 10)
    let inclusive: i32 = rng.gen_range(1..=100);  // [1, 100]
    let negative: i32 = rng.gen_range(-50..50);   // [-50, 50)
    let byte: u8 = rng.gen_range(0..=255);        // [0, 255]
    
    println!("Small: {}, Inclusive: {}, Negative: {}, Byte: {}", 
             small, inclusive, negative, byte);
    
    // Float ranges
    let unit: f64 = rng.gen_range(0.0..1.0);      // [0.0, 1.0)
    let angle: f64 = rng.gen_range(0.0..std::f64::consts::TAU);
    let temp: f64 = rng.gen_range(-273.15..1000.0);
    
    println!("Unit: {:.4}, Angle: {:.4}, Temp: {:.2}", unit, angle, temp);
    
    // Range types can be stored
    let range = 10..20;
    let in_range: i32 = rng.gen_range(range);
    println!("In range: {}", in_range);
}

Random Elements from Collections

use rand::{Rng, seq::SliceRandom};
 
fn main() {
    let mut rng = rand::thread_rng();
    
    // Choose single element
    let choices = ['a', 'b', 'c', 'd', 'e'];
    let choice = choices.choose(&mut rng);
    println!("Chosen: {:?}", choice);
    
    // Choose multiple elements (with replacement)
    let samples: Vec<&char> = choices.choose_multiple(&mut rng, 3).collect();
    println!("Samples: {:?}", samples);
    
    // Choose multiple (without replacement)
    let mut shuffled = vec![1, 2, 3, 4, 5];
    shuffled.shuffle(&mut rng);
    println!("Shuffled: {:?}", shuffled);
    
    // Partial shuffle
    let mut numbers: Vec<i32> = (1..=100).collect();
    numbers.partial_shuffle(&mut rng, 5);
    println!("First 5 shuffled: {:?}", &numbers[..5]);
    
    // Choose from String
    let alphabet = "abcdefghijklmnopqrstuvwxyz";
    let random_char = alphabet.chars().choose(&mut rng);
    println!("Random letter: {:?}", random_char);
    
    // Weighted choice
    let weighted_choices = [
        ('a', 4),  // weight 4
        ('b', 1),  // weight 1
        ('c', 3),  // weight 3
    ];
    let weights: Vec<u32> = weighted_choices.iter().map(|(_, w)| *w).collect();
    let chars: Vec<char> = weighted_choices.iter().map(|(c, _)| *c).collect();
    
    let dist = rand::distributions::WeightedIndex::new(&weights).unwrap();
    let idx = dist.sample(&mut rng);
    println!("Weighted choice: {}", chars[idx]);
}

Distributions

use rand::Rng;
use rand::distributions::{Distribution, Uniform, Alphanumeric, Standard};
 
fn main() {
    let mut rng = rand::thread_rng();
    
    // Uniform distribution (reusable)
    let die = Uniform::from(1..=6);
    let rolls: Vec<i32> = (0..10).map(|_| die.sample(&mut rng)).collect();
    println!("Dice rolls: {:?}", rolls);
    
    // Uniform for floats
    let percent_dist = Uniform::from(0.0..100.0);
    let percents: Vec<f64> = (0..5).map(|_| percent_dist.sample(&mut rng)).collect();
    println!("Percents: {:?}", percents);
    
    // Standard normal distribution (mean=0, std=1)
    let normal = rand::distributions::StandardNormal;
    let normals: Vec<f64> = (0..5).map(|_| normal.sample(&mut rng)).collect();
    println!("Standard normal: {:?}", normals);
    
    // Normal distribution with custom parameters
    let normal = rand_distr::Normal::new(100.0, 15.0).unwrap();
    let iq_scores: Vec<f64> = (0..5).map(|_| normal.sample(&mut rng)).collect();
    println!("IQ scores: {:?}", iq_scores);
    
    // Alphanumeric characters
    let code: String = (0..8)
        .map(|_| rng.sample(Alphanumeric) as char)
        .collect();
    println!("Random code: {}", code);
    
    // Using Standard distribution (default for types)
    let values: Vec<i32> = (0..5).map(|_| rng.sample(Standard)).collect();
    println!("Standard samples: {:?}", values);
}

Seeded Generators for Reproducibility

use rand::{Rng, SeedableRng};
use rand::rngs::StdRng;
 
fn main() {
    // Create seeded generator from seed
    let seed = 42u64;
    let mut rng1 = StdRng::seed_from_u64(seed);
    let mut rng2 = StdRng::seed_from_u64(seed);
    
    // Both generators produce identical sequences
    let seq1: Vec<u32> = (0..5).map(|_| rng1.gen()).collect();
    let seq2: Vec<u32> = (0..5).map(|_| rng2.gen()).collect();
    println!("Sequence 1: {:?}", seq1);
    println!("Sequence 2: {:?}", seq2);
    println!("Match: {}", seq1 == seq2);
    
    // Seed from entropy (random seed)
    let mut rng = StdRng::from_entropy();
    let random_values: Vec<i32> = (0..5).map(|_| rng.gen()).collect();
    println!("From entropy: {:?}", random_values);
    
    // Use seed for reproducible simulations
    println!("\nReproducible simulation:");
    run_simulation(12345);
    run_simulation(12345);  // Same results
}
 
fn run_simulation(seed: u64) {
    let mut rng = StdRng::seed_from_u64(seed);
    let results: Vec<i32> = (0..5).map(|_| rng.gen_range(1..=100)).collect();
    println!("Simulation results: {:?}", results);
}

Custom Random Types

use rand::Rng;
use rand::distributions::{Distribution, Standard};
 
#[derive(Debug)]
struct Point {
    x: f64,
    y: f64,
}
 
// Implement Distribution for custom type
impl Distribution<Point> for Standard {
    fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> Point {
        Point {
            x: rng.gen(),
            y: rng.gen(),
        }
    }
}
 
#[derive(Debug)]
enum Direction {
    Up,
    Down,
    Left,
    Right,
}
 
impl Distribution<Direction> for Standard {
    fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> Direction {
        match rng.gen_range(0..4) {
            0 => Direction::Up,
            1 => Direction::Down,
            2 => Direction::Left,
            _ => Direction::Right,
        }
    }
}
 
#[derive(Debug)]
struct Color {
    r: u8,
    g: u8,
    b: u8,
}
 
impl Distribution<Color> for Standard {
    fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> Color {
        Color {
            r: rng.gen(),
            g: rng.gen(),
            b: rng.gen(),
        }
    }
}
 
fn main() {
    let mut rng = rand::thread_rng();
    
    // Generate random Point
    let point: Point = rng.gen();
    println!("Random point: ({:.2}, {:.2})", point.x, point.y);
    
    // Generate random Direction
    let dirs: Vec<Direction> = (0..5).map(|_| rng.gen()).collect();
    println!("Directions: {:?}", dirs);
    
    // Generate random Color
    let color: Color = rng.gen();
    println!("Random color: RGB({}, {}, {})", color.r, color.g, color.b);
}

Cryptographically Secure Random Numbers

use rand::Rng;
use rand::rngs::OsRng;
 
fn main() {
    // OsRng uses the operating system's secure random source
    let mut rng = OsRng;
    
    // Generate secure random bytes
    let mut key = [0u8; 32];
    rng.fill(&mut key);
    println!("Secure key: {:x?}", key);
    
    // Generate secure random numbers
    let secure_int: u64 = rng.gen();
    println!("Secure integer: {}", secure_int);
    
    // Generate secure token
    let token: String = (0..32)
        .map(|_| rng.gen::<u8>() as char)
        .collect();
    println!("Token (bytes as chars): {:?}", token);
    
    // Better: hex-encoded token
    let token_bytes: [u8; 16] = rng.gen();
    let hex_token: String = token_bytes.iter().map(|b| format!("{:02x}", b)).collect();
    println!("Hex token: {}", hex_token);
    
    // UUID-like identifier
    let uuid: [u8; 16] = rng.gen();
    println!("UUID bytes: {:02x?}", uuid);
}

Shuffling and Permutations

use rand::Rng;
use rand::seq::SliceRandom;
 
fn main() {
    let mut rng = rand::thread_rng();
    
    // Shuffle a vector in place
    let mut cards: 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♥",
    ];
    cards.shuffle(&mut rng);
    println!("Shuffled hand: {:?}", &cards[..5]);
    
    // Generate a random permutation
    let mut permutation: Vec<usize> = (0..10).collect();
    permutation.shuffle(&mut rng);
    println!("Permutation: {:?}", permutation);
    
    // Check if shuffled
    let original: Vec<i32> = (1..=10).collect();
    let mut shuffled = original.clone();
    shuffled.shuffle(&mut rng);
    println!("Original: {:?}", original);
    println!("Shuffled: {:?}", shuffled);
    println!("Is shuffled: {}", original != shuffled);
    
    // Sample without replacement
    let numbers: Vec<i32> = (1..=1000).collect();
    let sample: Vec<&i32> = numbers.choose_multiple(&mut rng, 5).collect();
    println!("Sample of 5 from 1000: {:?}", sample);
}

Common Use Cases

use rand::Rng;
use rand::distributions::Alphanumeric;
use rand::seq::IteratorRandom;
 
fn main() {
    let mut rng = rand::thread_rng();
    
    // Generate random password
    let password: String = (0..12)
        .map(|_| rng.sample(Alphanumeric) as char)
        .collect();
    println!("Password: {}", password);
    
    // Generate random hex string
    let hex: String = (0..8)
        .map(|_| format!("{:02x}", rng.gen::<u8>()))
        .collect();
    println!("Hex string: {}", hex);
    
    // Pick random element from iterator
    let random_number = (1..=1000).choose(&mut rng);
    println!("Random from iterator: {:?}", random_number);
    
    // Coin flip
    let heads = rng.gen_bool(0.5);  // 50% chance
    println!("Coin flip: {}", if heads { "Heads" } else { "Tails" });
    
    // Loaded die (30% chance of success)
    let success = rng.gen_bool(0.3);
    println!("Success (30%): {}", success);
    
    // Random percentage
    let pct = rng.gen_ratio(1, 4);  // 25% chance
    println!("25% chance: {}", pct);
    
    // Random index
    let items = vec!["apple", "banana", "cherry", "date", "elderberry"];
    let idx = rng.gen_range(0..items.len());
    println!("Random fruit: {}", items[idx]);
    
    // Fill array with random values
    let mut buffer = [0u8; 16];
    rng.fill(&mut buffer);
    println!("Random buffer: {:02x?}", buffer);
}

Simulation Example: Monte Carlo Pi Estimation

use rand::Rng;
 
fn main() {
    let mut rng = rand::thread_rng();
    
    let total_points = 1_000_000;
    let mut inside_circle = 0;
    
    for _ in 0..total_points {
        let x: f64 = rng.gen_range(-1.0..=1.0);
        let y: f64 = rng.gen_range(-1.0..=1.0);
        
        if x * x + y * y <= 1.0 {
            inside_circle += 1;
        }
    }
    
    let pi_estimate = 4.0 * (inside_circle as f64) / (total_points as f64);
    println!("Pi estimate: {:.6}", pi_estimate);
    println!("Actual pi:   {:.6}", std::f64::consts::PI);
    println!("Error:       {:.6}", (pi_estimate - std::f64::consts::PI).abs());
}

Summary

  • Use rand::random() for quick one-off random values
  • Use rand::thread_rng() for a thread-local generator (most common)
  • Call rng.gen::<T>() for a random value of type T
  • Use rng.gen_range(start..end) for values in a range (exclusive)
  • Use rng.gen_range(start..=end) for inclusive range
  • SliceRandom::choose() picks a random element from a slice
  • SliceRandom::shuffle() shuffles a slice in place
  • IteratorRandom::choose() picks from an iterator
  • WeightedIndex samples with probability weights
  • Uniform::from(range) creates a reusable distribution
  • rand_distr::Normal::new(mean, std) for normal distribution
  • Use StdRng::seed_from_u64(seed) for reproducible sequences
  • Use OsRng for cryptographically secure random numbers
  • Implement Distribution<T> for Standard to make custom types randomly generatable
  • rng.gen_bool(p) returns true with probability p
  • rng.fill(&mut slice) fills a buffer with random bytes