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:
rand::Rngtrait — core interface for generating random valuesrandom()function — quick one-off random valuesthread_rng()— thread-local generator (most common)- Distributions — uniform, normal, weighted, and custom distributions
- 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 typeT - 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 sliceSliceRandom::shuffle()shuffles a slice in placeIteratorRandom::choose()picks from an iteratorWeightedIndexsamples with probability weightsUniform::from(range)creates a reusable distributionrand_distr::Normal::new(mean, std)for normal distribution- Use
StdRng::seed_from_u64(seed)for reproducible sequences - Use
OsRngfor cryptographically secure random numbers - Implement
Distribution<T> for Standardto make custom types randomly generatable rng.gen_bool(p)returnstruewith probabilityprng.fill(&mut slice)fills a buffer with random bytes
