What is the difference between rand::Rng::gen_range and gen for generating random values with constraints?
gen samples uniformly from the entire range of a type, while gen_range samples uniformly from a specified intervalβoffering precise control over the output bounds and better efficiency for bounded random values. The distinction matters when you need values within specific constraints, as gen_range avoids the overhead of generating then filtering or modifying out-of-range values. Understanding these methods helps you write more efficient and correct random value generation.
The Two Methods
use rand::{Rng, SeedableRng};
use rand::rngs::StdRng;
fn main() {
let mut rng = StdRng::seed_from_u64(42);
// gen<T>() - samples from the entire type range
// For u8: values from 0 to 255
let byte: u8 = rng.gen();
// For u32: values from 0 to u32::MAX
let num: u32 = rng.gen();
// For f64: values from 0.0 to 1.0 (exclusive upper bound)
let float: f64 = rng.gen();
// gen_range(range) - samples from a specified range
// For integers: inclusive start, exclusive end
let dice: u8 = rng.gen_range(1..=6); // 1 to 6 inclusive
// For floats: exclusive upper bound
let percentage: f64 = rng.gen_range(0.0..100.0);
println!("byte: {}, num: {}, float: {}", byte, num, float);
println!("dice: {}, percentage: {}", dice, percentage);
}gen produces values across the type's full range; gen_range constrains output to specified bounds.
The Full Range Problem with gen
use rand::{Rng, SeedableRng};
use rand::rngs::StdRng;
fn example() {
let mut rng = StdRng::seed_from_u64(42);
// gen() for integers covers the entire type range
let any_u8: u8 = rng.gen(); // 0 to 255
let any_u32: u32 = rng.gen(); // 0 to 4,294,967,295
let any_i32: i32 = rng.gen(); // -2,147,483,648 to 2,147,483,647
// gen() for floats has a defined range
let f: f64 = rng.gen(); // 0.0 to 1.0 (standard distribution)
// If you need values in a specific range using gen():
// WRONG: Modulo bias!
let dice_wrong: u8 = rng.gen::<u8>() % 6 + 1;
// This has bias: values 0-1 appear more often than 2-5
// because 256 is not evenly divisible by 6
// BETTER: Use gen_range (no bias)
let dice_correct: u8 = rng.gen_range(1..=6);
// Uniform sampling within 1-6, no bias
// Also WRONG: Rejection sampling manually is inefficient
let dice_rejection: u8 = loop {
let val: u8 = rng.gen();
if val >= 1 && val <= 6 {
break val;
}
// This rejects 250 out of 256 values on average!
};
}gen requires extra work (and potential bugs) for bounded ranges; gen_range handles this correctly.
Uniform Distribution Guarantees
use rand::{Rng, SeedableRng};
use rand::rngs::StdRng;
fn distribution_demo() {
let mut rng = StdRng::seed_from_u64(42);
// gen() uses the Standard distribution
// For integers: Uniform over entire type range
// For floats: Standard distribution (0.0 to 1.0)
// gen_range() uses Uniform distribution within the range
// This is mathematically correct uniform sampling
// Example: Rolling a die 1_000_000 times
let mut counts = [0u32; 6];
for _ in 0..1_000_000 {
let roll: u8 = rng.gen_range(1..=6);
counts[(roll - 1) as usize] += 1;
}
// Each value should appear ~166,667 times (within statistical variance)
// Uniform distribution guarantees equal probability
for (i, count) in counts.iter().enumerate() {
println!("Face {}: {} occurrences", i + 1, count);
}
// Compare with biased approach:
let mut biased_counts = [0u32; 6];
for _ in 0..1_000_000 {
let val: u8 = rng.gen();
let roll = (val % 6) + 1; // Biased!
biased_counts[(roll - 1) as usize] += 1;
}
// The biased approach shows non-uniform distribution
// Some values appear more often than others
}gen_range guarantees uniform distribution; manual approaches like modulo introduce bias.
Range Syntax and Bounds
use rand::{Rng, SeedableRng};
use rand::rngs::StdRng;
fn range_examples() {
let mut rng = StdRng::seed_from_u64(42);
// Integer ranges
// Exclusive end: 0..10 means [0, 10)
let exclusive: u32 = rng.gen_range(0..10); // 0 to 9
// Inclusive end: 0..=10 means [0, 10]
let inclusive: u32 = rng.gen_range(0..=10); // 0 to 10
// Array index generation (exclusive is common)
let items = ["apple", "banana", "cherry", "date"];
let idx: usize = rng.gen_range(0..items.len()); // Valid index
println!("Random fruit: {}", items[idx]);
// Dice roll (inclusive is natural)
let dice: u8 = rng.gen_range(1..=6); // 1 through 6
// Float ranges
// Floats only support exclusive upper bound
let percent: f64 = rng.gen_range(0.0..100.0); // 0.0 to <100.0
// Common patterns
let angle: f64 = rng.gen_range(0.0..std::f64::consts::TAU); // Full rotation
// Range bounds can be expressions
let min = 10;
let max = 20;
let val: i32 = rng.gen_range(min..=max);
// Single value range (always returns that value)
let always_five: i32 = rng.gen_range(5..=5); // Always 5
let also_five: i32 = rng.gen_range(5..6); // Also always 5
}gen_range supports both exclusive (..) and inclusive (..=) bounds for integers; floats use exclusive upper bounds.
Efficiency Considerations
use rand::{Rng, SeedableRng};
use rand::rngs::StdRng;
fn efficiency_comparison() {
let mut rng = StdRng::seed_from_u64(42);
// Scenario: Need random value 0-9
// Approach 1: gen() then filter (inefficient)
let value1: u8 = loop {
let v: u8 = rng.gen();
if v < 10 {
break v;
}
// Expected iterations: 256/10 = 25.6
// Wasteful! Rejects 246 out of 256 values
};
// Approach 2: gen_range (efficient)
let value2: u8 = rng.gen_range(0..10);
// Uses efficient algorithm that maps random bits to range
// No rejection sampling in most cases
// When gen_range is more efficient:
// - Small ranges relative to type size (e.g., 0-9 in u8)
// - Avoids modulo bias
// - Single method call vs. loop
// When gen might be acceptable:
// - You need values across the entire type range
// - Type conversion is needed anyway
// Performance example: generating array indices
const SIZE: usize = 100;
let mut arr = [0u32; SIZE];
// Efficient: gen_range
for _ in 0..10_000 {
let idx: usize = rng.gen_range(0..SIZE);
arr[idx] += 1;
}
// The gen_range implementation uses:
// - For small ranges: efficient multiplication-based mapping
// - For larger ranges: rejection sampling with narrow bounds
// - Generally faster than gen() + rejection for constrained ranges
}gen_range is more efficient for bounded ranges because it avoids unnecessary rejection sampling.
Type Inference and Turbofish
use rand::{Rng, SeedableRng};
use rand::rngs::StdRng;
fn type_inference() {
let mut rng = StdRng::seed_from_u64(42);
// gen() requires type annotation (no constraints from method)
// Via type annotation:
let num: u32 = rng.gen();
// Via turbofish:
let num = rng.gen::<u32>();
// Via context:
fn takes_u32(n: u32) { /* ... */ }
takes_u32(rng.gen());
// gen_range() infers type from range
// Range type determines output:
let val = rng.gen_range(0u8..100); // u8
let val = rng.gen_range(0u32..100); // u32
let val = rng.gen_range(0.0..1.0); // f64
// Explicit types when needed:
let val: u64 = rng.gen_range(0..1_000_000);
// Mixed types - the range determines output:
let val = rng.gen_range(0u8..=255); // u8
// val is u8, inferred from range bounds
// Error if bounds don't match:
// let val = rng.gen_range(0u8..100u32); // Compile error!
// Mismatched types in range
}gen often requires explicit type annotation; gen_range infers type from range bounds.
Working with Different Types
use rand::{Rng, SeedableRng};
use rand::rngs::StdRng;
fn different_types() {
let mut rng = StdRng::seed_from_u64(42);
// Integer types
let i8_val: i8 = rng.gen(); // -128 to 127
let u8_val: u8 = rng.gen(); // 0 to 255
let i16_val: i16 = rng.gen(); // -32768 to 32767
let u64_val: u64 = rng.gen(); // 0 to u64::MAX
let i128_val: i128 = rng.gen(); // Full i128 range
// Bounded integers:
let small: u8 = rng.gen_range(0..10); // 0 to 9
let signed: i32 = rng.gen_range(-100..100); // -100 to 99
let big: u64 = rng.gen_range(0..1_000_000); // 0 to 999999
// Float types
let f: f64 = rng.gen(); // 0.0 to 1.0 (exclusive)
let f32_val: f32 = rng.gen(); // Same for f32
// Bounded floats:
let temp: f64 = rng.gen_range(-273.15..1000.0);
let angle: f64 = rng.gen_range(0.0..std::f64::consts::TAU);
// Boolean (via gen)
let coin_flip: bool = rng.gen(); // 50/50 chance
// Tuples and arrays (via gen)
let pair: (u8, u8) = rng.gen(); // Two random u8s
let coords: (f64, f64) = rng.gen(); // Two random f64s
let bytes: [u8; 16] = rng.gen(); // Random 16-byte array
let uuid_bytes: [u8; 16] = rng.gen(); // UUID-sized array
// Alphanumeric (via gen_ascii_chars, similar to gen_range)
let id: String = rng.sample_iter(rand::distributions::Alphanumeric)
.take(10)
.map(char::from)
.collect();
}gen works with any type implementing Standard distribution; gen_range works with numeric types implementing Uniform.
Distributions and Sampling
use rand::{Rng, SeedableRng, distributions::{Distribution, Uniform, Standard}};
use rand::rngs::StdRng;
fn distributions() {
let mut rng = StdRng::seed_from_u64(42);
// gen() uses the Standard distribution
// Standard is implemented for many types with "sensible defaults"
// gen_range() creates a Uniform distribution internally
// You can create Uniform distributions explicitly:
let die = Uniform::new_inclusive(1, 6);
let roll1: u8 = die.sample(&mut rng);
let roll2: u8 = die.sample(&mut rng);
let roll3: u8 = die.sample(&mut rng);
// This is equivalent to gen_range(1..=6) but reuses the distribution
// Useful when sampling many times from the same range
// For comparison:
// One-off: gen_range
let val = rng.gen_range(0..100);
// Repeated: create distribution once
let dist = Uniform::new(0, 100);
for _ in 0..1000 {
let val: u32 = dist.sample(&mut rng);
// Do something with val
}
// Distribution approach avoids reconstructing the range each call
// gen_range internally creates Uniform each time
// Standard distribution for custom types:
#[derive(Debug)]
struct Point { x: f64, y: f64 }
impl Distribution<Point> for Standard {
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> Point {
Point {
x: rng.gen(),
y: rng.gen(),
}
}
}
let point: Point = rng.gen(); // Uses Standard distribution
println!("Point: ({}, {})", point.x, point.y);
}gen_range internally creates a Uniform distribution; for repeated sampling, create the distribution explicitly.
Common Patterns
use rand::{Rng, SeedableRng, seq::SliceRandom};
use rand::rngs::StdRng;
fn patterns() {
let mut rng = StdRng::seed_from_u64(42);
// Pattern 1: Array indexing
let items = vec!["a", "b", "c", "d", "e"];
let random_item = &items[rng.gen_range(0..items.len())];
// Pattern 2: Dice/games (inclusive)
let dice = rng.gen_range(1..=6);
let coin_flip: bool = rng.gen(); // gen is simpler for bool
// Pattern 3: Percentage/probability
let chance: f64 = rng.gen_range(0.0..100.0);
if chance < 30.0 {
println!("30% chance event occurred");
}
// Pattern 4: Random selection from slice
// Use SliceRandom trait instead of gen_range
let chosen = items.choose(&mut rng).unwrap();
// Pattern 5: Shuffling (uses internal randomness)
let mut shuffled = items.clone();
shuffled.shuffle(&mut rng);
// Pattern 6: Generating IDs
let id: u64 = rng.gen_range(1..1_000_000);
// Pattern 7: Random boolean with probability
let success = rng.gen_bool(0.25); // 25% chance
// This is cleaner than gen_range for boolean outcomes
// Pattern 8: Generating ranges of random values
let values: Vec<u32> = (0..10).map(|_| rng.gen_range(0..100)).collect();
// Pattern 9: Fill buffer with random bytes
let mut buffer = [0u8; 1024];
rng.fill(&mut buffer); // More efficient than gen() in a loop
}Use gen_range for bounded numeric values; use specialized methods for booleans and sequences.
Edge Cases and Panics
use rand::{Rng, SeedableRng};
use rand::rngs::StdRng;
fn edge_cases() {
let mut rng = StdRng::seed_from_u64(42);
// gen_range panics on empty ranges
// These PANIC:
// let x: u8 = rng.gen_range(10..5); // Panic: empty range
// let x: u8 = rng.gen_range(5..5); // Panic: empty range (exclusive)
// These WORK:
let x: u8 = rng.gen_range(5..=5); // Returns 5 (inclusive)
let x: u8 = rng.gen_range(5..6); // Returns 5
// Float edge cases:
// NaN and infinity are generally avoided
// gen() for floats returns [0.0, 1.0) typically
// Never returns NaN or infinity
// gen_range for floats:
let f: f64 = rng.gen_range(0.0..1.0);
// Upper bound is exclusive
// Integer overflow (very rare):
let max = u32::MAX;
// let val = rng.gen_range(0..=max); // Fine
// Internal calculations handle overflow correctly
// Signed integers:
let neg: i32 = rng.gen_range(-100..100); // -100 to 99
let all: i32 = rng.gen(); // Full i32 range
}gen_range panics on empty ranges; use inclusive bounds (..=) for single values.
Practical Example: Game Logic
use rand::{Rng, SeedableRng};
use rand::rngs::StdRng;
#[derive(Debug)]
struct Character {
name: String,
health: u32,
attack: u32,
defense: u32,
}
impl Character {
fn new_random(name: &str) -> Self {
let mut rng = StdRng::seed_from_u64(42);
// Use gen_range for bounded game values
Character {
name: name.to_string(),
health: rng.gen_range(50..=100), // 50-100 HP
attack: rng.gen_range(5..=15), // 5-15 ATK
defense: rng.gen_range(3..=10), // 3-10 DEF
}
}
fn attack_roll(&self, rng: &mut impl Rng) -> u32 {
// Attack damage: base attack + random bonus
let bonus = rng.gen_range(0..=5); // 0-5 bonus damage
self.attack + bonus
}
fn critical_hit(&self, rng: &mut impl Rng) -> (u32, bool) {
// 10% critical hit chance
let roll = rng.gen_range(1..=100);
if roll <= 10 {
(self.attack * 2, true)
} else {
(self.attack, false)
}
}
}
fn game_example() {
let mut rng = StdRng::seed_from_u64(42);
let hero = Character::new_random("Hero");
let monster = Character::new_random("Goblin");
println!("{:?}", hero);
println!("{:?}", monster);
// Combat simulation
for round in 1..=3 {
let (damage, crit) = hero.attack_roll(&mut rng);
println!("Round {}: {} damage{}", round, damage, if crit { " (CRIT!)" } else { "" });
}
}Game logic benefits from gen_range for bounded stats and probability-based outcomes.
Synthesis
Quick reference:
use rand::Rng;
// gen() - Full type range, Standard distribution
let byte: u8 = rng.gen(); // 0 to 255
let num: u32 = rng.gen(); // 0 to u32::MAX
let signed: i32 = rng.gen(); // Full i32 range
let float: f64 = rng.gen(); // 0.0 to 1.0
let coin: bool = rng.gen(); // true/false (50/50)
let bytes: [u8; 16] = rng.gen(); // Random array
// gen_range() - Bounded range, Uniform distribution
let dice: u8 = rng.gen_range(1..=6); // 1 to 6 inclusive
let index: usize = rng.gen_range(0..len); // 0 to len-1 (exclusive)
let percent: f64 = rng.gen_range(0.0..100.0); // 0.0 to <100.0
let neg: i32 = rng.gen_range(-10..10); // -10 to 9
// Decision matrix:
// ββββββββββββββββββββββββββββββββββββββ¬βββββββββββββββ¬βββββββββββββββββ
// β Need β Use β Why β
// ββββββββββββββββββββββββββββββββββββββΌβββββββββββββββΌβββββββββββββββββ€
// β Full type range β gen() β Simple β
// β Bounded integer range β gen_range() β No bias β
// β Array index β gen_range() β Correct bounds β
// β Dice/game values β gen_range() β Inclusive feel β
// β Percentage/probability β gen_range() β Clear intent β
// β Boolean β gen() β 50/50 default β
// β Weighted boolean β gen_bool(p) β Explicit prob β
// β Random choice from collection β choose() β Built-in β
// β Many values from same range β Uniform β Reuse dist β
// ββββββββββββββββββββββββββββββββββββββ΄βββββββββββββββ΄βββββββββββββββββ
// Anti-patterns:
// β rng.gen::<u8>() % 6 // Biased!
// β rng.gen::<u32>() % 100 // Biased!
// β
rng.gen_range(0..6) // Uniform
// Performance tips:
// - gen_range(0..small) is efficient for small ranges
// - For many samples from same range, create Uniform distribution
// - fill() is faster than gen() in a loop for buffersKey insight: gen and gen_range serve different purposes despite both producing random values. gen samples from the entire valid range of a typeβit's the "I need any random value" method. gen_range samples uniformly from a specified interval, handling the mathematical complexity of uniform distribution within bounds. The critical difference emerges when you need bounded values: using gen() followed by % or rejection creates subtle biases, while gen_range guarantees true uniform sampling. For game dice, array indices, percentages, and any constrained random values, gen_range is both cleaner and correct. Use gen when the full type range is appropriate, like generating random UUID bytes, fill buffers, or boolean coin flips.
