Loading pageā¦
Rust walkthroughs
Loading pageā¦
rand::Rng::gen and gen_range for random value generation?rand::Rng::gen generates uniformly distributed random values for any type implementing the Standard distribution, including all primitive types and many compound types, while gen_range generates random values within an inclusive range for types implementing SampleUniform. The fundamental distinction is that gen samples from the full range of possible values for a type, making it ideal for generating any valid instance, whereas gen_range constrains output to a specified interval, making it suitable for bounded values like dice rolls or percentages. The gen method uses type inference to determine the output type, requiring either explicit type annotation or context, while gen_range takes explicit bounds as arguments. For numeric types, gen produces values across the entire type's rangeāfrom MIN to MAXāwhile gen_range produces values from low to high inclusive, enabling control over the output domain without manual scaling or rejection sampling.
use rand::{thread_rng, Rng};
fn main() {
let mut rng = thread_rng();
// gen() samples from the full range of the type
// Type must be specified through inference or annotation
let random_i32: i32 = rng.gen();
let random_u64: u64 = rng.gen();
let random_f64: f64 = rng.gen();
let random_bool: bool = rng.gen();
let random_char: char = rng.gen();
println!("i32: {}", random_i32);
println!("u64: {}", random_u64);
println!("f64: {}", random_f64);
println!("bool: {}", random_bool);
println!("char: {}", random_char);
// gen() works for any type implementing Standard distribution
// The output range is the entire valid range of the type
}gen() generates values across the full range of the specified type using Standard distribution.
use rand::{thread_rng, Rng};
fn main() {
let mut rng = thread_rng();
// gen_range() samples from a specified range
// Bounds are specified as arguments
let roll: i32 = rng.gen_range(1..=6); // Dice roll 1-6
let percent: f64 = rng.gen_range(0.0..=100.0); // Percentage
let letter: char = rng.gen_range('a'..='z'); // Lowercase letter
println!("Dice: {}", roll);
println!("Percent: {:.1}%", percent);
println!("Letter: {}", letter);
// gen_range() constrains output to the specified range
// Inclusive range syntax ..= includes the upper bound
}gen_range() generates values within specified bounds, inclusive of the upper bound.
use rand::{thread_rng, Rng};
fn main() {
let mut rng = thread_rng();
// gen() requires explicit type annotation or inference context
let value: u8 = rng.gen(); // Explicit annotation
println!("u8: {}", value);
// Without annotation, type must be inferrable
// let unknown = rng.gen(); // Error: cannot infer type
// Function argument provides inference context
fn takes_u32(_: u32) {}
takes_u32(rng.gen()); // Type inferred from argument
// gen_range() infers type from bounds
let in_range = rng.gen_range(0..10); // Type inferred as usize
println!("0-9: {}", in_range);
let float_range = rng.gen_range(0.0..1.0); // Type inferred as f64
println!("0.0-1.0: {}", float_range);
// Bounds determine the type
let explicit: u8 = rng.gen_range(0..=255);
println!("u8: {}", explicit);
}gen() requires type context; gen_range() infers type from bound arguments.
use rand::{thread_rng, Rng};
fn main() {
let mut rng = thread_rng();
// gen() - full type range
let i32_full: i32 = rng.gen(); // i32::MIN to i32::MAX
let u8_full: u8 = rng.gen(); // 0 to 255
let f64_full: f64 = rng.gen(); // 0.0 to 1.0 (exclusive)
// gen_range() - specified range
let dice: i32 = rng.gen_range(1..=6); // 1, 2, 3, 4, 5, or 6
let digit: u8 = rng.gen_range(0..10); // 0-9
let fraction: f64 = rng.gen_range(0.0..=1.0); // 0.0 to 1.0 inclusive
println!("i32 range: {}..= {}", i32::MIN, i32::MAX);
println!("u8 range: 0..=255");
println!("f64 gen range: 0.0..1.0 (not including 1.0)");
// f64::gen() uses Standard which samples [0, 1)
// gen_range(0.0..1.0) also samples [0, 1)
// gen_range(0.0..=1.0) includes 1.0
}gen() uses full type range; gen_range() uses caller-specified bounds.
use rand::{thread_rng, Rng};
fn main() {
let mut rng = thread_rng();
// Integer gen() - uniform across entire range
let i8_val: i8 = rng.gen(); // -128 to 127
let u16_val: u16 = rng.gen(); // 0 to 65535
// Integer gen_range() - uniform within bounds
let small: i32 = rng.gen_range(0..10); // 0-9 (exclusive)
let inclusive: i32 = rng.gen_range(0..=9); // 0-9 (inclusive)
// Both produce the same set: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9
// But semantics differ:
println!("Exclusive 0..10: {}", rng.gen_range::<i32, _>(0..10));
println!("Inclusive 0..=9: {}", rng.gen_range::<i32, _>(0..=9));
// Common pattern: array index
let arr = [1, 2, 3, 4, 5];
let idx = rng.gen_range(0..arr.len()); // Valid index
println!("Random element: {}", arr[idx]);
// gen() for i8 would rarely produce valid array indices
let i8_idx: i8 = rng.gen(); // -128 to 127, mostly invalid
}Integer gen() covers full type range; gen_range() produces bounded values.
use rand::{thread_rng, Rng};
fn main() {
let mut rng = thread_rng();
// Float gen() - samples from Standard distribution
// Standard for f32/f64 produces [0.0, 1.0) - NOT full type range
let f64_val: f64 = rng.gen();
println!("f64 gen(): {}", f64_val);
// Always in [0.0, 1.0)
// Float gen_range() - samples within specified bounds
let small: f64 = rng.gen_range(0.0..1.0); // [0.0, 1.0)
let inclusive: f64 = rng.gen_range(0.0..=1.0); // [0.0, 1.0]
let larger: f64 = rng.gen_range(-10.0..10.0); // [-10.0, 10.0)
println!("gen_range 0.0..1.0: {}", small);
println!("gen_range 0.0..=1.0: {}", inclusive);
println!("gen_range -10.0..10.0: {}", larger);
// Key difference: gen() for floats is [0, 1)
// gen_range() can produce any specified interval
// For percentage (0-100)
let percent = rng.gen_range(0.0..=100.0);
println!("Percentage: {:.2}%", percent);
}gen() for floats produces values in [0.0, 1.0); gen_range() enables any interval.
use rand::{thread_rng, Rng};
fn main() {
let mut rng = thread_rng();
// Boolean gen() - 50/50 probability
let coin_flip: bool = rng.gen();
println!("Coin flip: {}", coin_flip);
// No gen_range for bool - only two values
// Char gen() - random Unicode scalar value
let any_char: char = rng.gen();
println!("Random Unicode: {:?}", any_char);
// Can be any valid char, including control characters
// Char gen_range() - specific character ranges
let digit = rng.gen_range('0'..='9');
let lowercase = rng.gen_range('a'..='z');
let uppercase = rng.gen_range('A'..='Z');
println!("Digit: {}", digit);
println!("Lowercase: {}", lowercase);
println!("Uppercase: {}", uppercase);
// Useful for generating identifiers
let letter = rng.gen_range('a'..='z');
let letter_or_digit = if rng.gen() {
rng.gen_range('a'..='z')
} else {
rng.gen_range('0'..='9')
};
}gen() for char produces any Unicode; gen_range() constrains to specific character sets.
use rand::{thread_rng, Rng, distributions::Standard};
fn main() {
let mut rng = thread_rng();
// gen() works for compound types implementing Standard
let arr: [u8; 4] = rng.gen();
println!("Array: {:?}", arr);
let tuple: (i32, f64, bool) = rng.gen();
println!("Tuple: {:?}", tuple);
let option: Option<u32> = rng.gen();
println!("Option: {:?}", option);
// Standard distribution for Option:
// - 50% None, 50% Some(random value)
// gen_range() only works for scalar types
// No direct support for arrays or tuples
// Must use gen() for compound types or generate element by element
let bounded_arr: [u8; 4] = [
rng.gen_range(0..=10),
rng.gen_range(0..=10),
rng.gen_range(0..=10),
rng.gen_range(0..=10),
];
println!("Bounded array: {:?}", bounded_arr);
}gen() supports compound types; gen_range() requires element-by-element generation.
use rand::{thread_rng, Rng};
use rand::distributions::{Distribution, Standard, Uniform};
fn main() {
let mut rng = thread_rng();
// Standard distribution is what gen() uses internally
// For custom distributions, use sample() directly
// Uniform distribution with bounds (like gen_range)
let uniform = Uniform::new(0, 100);
let value: i32 = uniform.sample(&mut rng);
println!("Uniform 0-99: {}", value);
// Uniform::new_inclusive for inclusive bounds
let inclusive = Uniform::new_inclusive(0, 100);
let value: i32 = inclusive.sample(&mut rng);
println!("Uniform 0-100: {}", value);
// The difference:
// gen() -> Standard distribution
// gen_range() -> creates temporary Uniform distribution
// Both produce the same result for bounded integers
// Pre-creating Uniform is more efficient for repeated sampling
let range = Uniform::new(0, 1000);
let samples: Vec<i32> = (0..10).map(|_| range.sample(&mut rng)).collect();
println!("Samples: {:?}", samples);
}gen_range() internally uses Uniform distribution; pre-creating Uniform is more efficient.
use rand::{thread_rng, Rng};
use rand::distributions::Uniform;
use std::time::Instant;
fn main() {
let mut rng = thread_rng();
// gen() - single sample from Standard distribution
let start = Instant::now();
let mut sum: u64 = 0;
for _ in 0..1_000_000 {
let val: u32 = rng.gen();
sum += val as u64;
}
let gen_time = start.elapsed();
println!("gen() time: {:?}", gen_time);
// gen_range() - creates Uniform distribution each call
let start = Instant::now();
let mut sum: u64 = 0;
for _ in 0..1_000_000 {
let val: u32 = rng.gen_range(0..1000);
sum += val as u64;
}
let gen_range_time = start.elapsed();
println!("gen_range() time: {:?}", gen_range_time);
// Pre-created Uniform distribution - most efficient
let start = Instant::Instant::now();
let uniform = Uniform::new(0u32, 1000);
let mut sum: u64 = 0;
for _ in 0..1_000_000 {
let val: u32 = uniform.sample(&mut rng);
sum += val as u64;
}
let uniform_time = start.elapsed();
println!("Uniform sample() time: {:?}", uniform_time);
// Pre-creating Uniform avoids distribution setup overhead
}Pre-creating Uniform distribution is most efficient for repeated bounded sampling.
use rand::{thread_rng, Rng};
fn main() {
let mut rng = thread_rng();
// gen() never panics - always produces valid values
let val: i32 = rng.gen(); // Cannot fail
let val: f64 = rng.gen(); // Cannot fail
// gen_range() can panic with invalid bounds
// Empty range (start > end) panics
// let bad = rng.gen_range(10..5); // panic!
// Empty range (start == end with exclusive)
// let bad = rng.gen_range(5..5); // panic!
// Valid range (start == end with inclusive)
let same = rng.gen_range(5..=5); // Always returns 5
println!("Same value: {}", same);
// For integers, prefer inclusive bounds when range might be single element
let single = rng.gen_range(0..=0); // Always 0
println!("Single: {}", single);
// Float edge cases
let zero_to_one = rng.gen_range(0.0f64..1.0); // Valid
let reversed = rng.gen_range(1.0f64..0.0); // Empty, panics
// NaN and infinity are handled specially
// Range with NaN panics
// let nan_range = rng.gen_range(0.0..f64::NAN); // panic!
}gen() never panics; gen_range() panics on empty or invalid ranges.
use rand::{thread_rng, Rng};
fn main() {
let mut rng = thread_rng();
// gen_range() supports multiple range syntaxes
// Exclusive (half-open) range
let exclusive: i32 = rng.gen_range(0..10); // 0-9
// Inclusive range
let inclusive: i32 = rng.gen_range(0..=10); // 0-10
// Bound inference
let inferred: i32 = rng.gen_range(0..100); // Type inferred
// Explicit bounds
let explicit: u8 = rng.gen_range(0u8..=255u8);
// Expression bounds
let min = 10;
let max = 20;
let dynamic: i32 = rng.gen_range(min..=max);
// Both bounds must be same type
// let mismatch = rng.gen_range(0i32..10i64); // Compile error
// gen() requires type annotation, not bounds
let typed: i32 = rng.gen();
let _ = rng.gen::<i32>(); // Turbofish syntax
}gen_range() uses range expressions; gen() uses type annotations.
use rand::{thread_rng, Rng};
fn main() {
let mut rng = thread_rng();
// gen() is appropriate when:
// 1. Full type range is valid
// 2. Random ID or salt generation
// 3. Test data generation
// 4. UUID components
// Random ID generation
let id: u64 = rng.gen();
println!("ID: {}", id);
// Random salt for hashing
let salt: [u8; 16] = rng.gen();
println!("Salt: {:?}", salt);
// Random session token
let token: [u8; 32] = rng.gen();
println!("Token: {:?}", token);
// Random IPv4 address components (each byte is 0-255)
let ip_bytes: [u8; 4] = rng.gen();
println!("Random IP: {}.{}.{}.{}",
ip_bytes[0], ip_bytes[1], ip_bytes[2], ip_bytes[3]);
// Random port number (need full u16 range)
let port: u16 = rng.gen();
println!("Port: {}", port);
// Test data generation
fn generate_test_user() -> (String, u32, bool) {
let mut rng = thread_rng();
let id: u32 = rng.gen();
let active: bool = rng.gen();
(format!("user_{}", id), id, active)
}
}Use gen() when full type range is valid and bounds would be artificial.
use rand::{thread_rng, Rng};
fn main() {
let mut rng = thread_rng();
// gen_range() is appropriate when:
// 1. Values must be within specific bounds
// 2. Business logic constrains valid range
// 3. Array indexing is needed
// 4. User-facing random values
// Dice games
let dice: u8 = rng.gen_range(1..=6);
println!("Dice roll: {}", dice);
// Card games
let card = rng.gen_range(0..52);
println!("Card index: {}", card);
// Array indexing
let colors = ["red", "green", "blue", "yellow"];
let color = colors[rng.gen_range(0..colors.len())];
println!("Random color: {}", color);
// Percentages and probabilities
let percent = rng.gen_range(0..=100);
println!("Random percent: {}%", percent);
// Timings and delays
let delay_ms = rng.gen_range(100..=500);
println!("Delay: {}ms", delay_ms);
// Coordinates in bounded space
let x = rng.gen_range(0..=100);
let y = rng.gen_range(0..=100);
println!("Position: ({}, {})", x, y);
// Range-based validation
let temperature = rng.gen_range(-20..=40); // Celsius
println!("Temperature: {}°C", temperature);
}Use gen_range() when values must satisfy business constraints.
use rand::{thread_rng, Rng};
fn main() {
let mut rng = thread_rng();
// Use gen() for unbounded, gen_range() for bounded
// Random struct with mixed fields
struct NetworkPacket {
id: u64, // Full range - any valid ID
port: u16, // Full range - any valid port
priority: u8, // Bounded - 0-7 for QoS
payload: Vec<u8>, // Random length and content
}
fn generate_packet() -> NetworkPacket {
let mut rng = thread_rng();
NetworkPacket {
id: rng.gen(), // gen for full range
port: rng.gen(), // gen for full range
priority: rng.gen_range(0..=7), // gen_range for bounded
payload: (0..rng.gen_range(64..=1500))
.map(|_| rng.gen())
.collect(),
}
}
let packet = generate_packet();
println!("ID: {}", packet.id);
println!("Port: {}", packet.port);
println!("Priority: {}", packet.priority);
println!("Payload len: {}", packet.payload.len());
}Combine gen() for unbounded fields and gen_range() for bounded constraints.
use rand::{thread_rng, Rng, SeedableRng};
use rand::rngs::StdRng;
fn main() {
// thread_rng() - default, thread-local, efficient
let mut rng = thread_rng();
let val1: i32 = rng.gen();
let val2: i32 = rng.gen_range(0..100);
println!("ThreadRng gen: {}, gen_range: {}", val1, val2);
// StdRng - deterministic, seedable, reproducible
let mut rng = StdRng::seed_from_u64(42);
let val3: i32 = rng.gen();
let val4: i32 = rng.gen_range(0..100);
println!("StdRng gen: {}, gen_range: {}", val3, val4);
// Same seed produces same sequence
let mut rng2 = StdRng::seed_from_u64(42);
let val5: i32 = rng2.gen();
let val6: i32 = rng2.gen_range(0..100);
println!("Deterministic: {}, {}", val5, val6);
// val3 == val5, val4 == val6
// Both gen() and gen_range() work with any Rng implementation
}gen() and gen_range() work with any Rng implementation.
| Aspect | gen() | gen_range() |
|--------|---------|---------------|
| Type specification | Type annotation or inference | Bounds determine type |
| Output range | Full type range | Specified bounds |
| Distribution | Standard | Uniform |
| Bounds argument | None | Required |
| Float output | [0.0, 1.0) for f32/f64 | Any specified range |
| Integer output | MIN to MAX | low to high |
| Compound types | Supported (arrays, tuples, Option) | Not supported directly |
| Panics | Never | Empty range |
| Use case | Random IDs, salts, full-range values | Bounded values, game logic |
The fundamental distinction between gen and gen_range is that gen samples from the full range of possible values for a type while gen_range constrains output to a specified interval. For primitive types like i32, gen() produces values across the entire type range from i32::MIN to i32::MAX, which is rarely what application logic requiresādice rolls don't produce values in the billions, percentages aren't negative, and array indices can't exceed collection bounds. This makes gen_range() the more commonly used method in practice for numeric types.
For floating-point types, gen() uses the Standard distribution which samples from [0.0, 1.0) rather than the full IEEE 754 range, making it suitable for probabilities and normalized values. gen_range() extends this to any specified interval, enabling negative ranges, percentages, or custom domains. The distinction between gen() for floats and gen_range(0.0..1.0) is subtleāboth produce values in approximately the same rangeābut gen_range(0.0..=1.0) includes 1.0 while gen() does not.
The type inference difference is ergonomic: gen() requires the caller to specify the type through annotation or context because there's no other information available, while gen_range() infers type from the bound arguments, making rng.gen_range(0..100) infer i32 and rng.gen_range(0.0..1.0) infer f64. For compound types like arrays and tuples, only gen() works directly because these types implement Standard distribution. Using gen_range() for compound types requires element-by-element generation.
The performance difference is negligible for occasional sampling, but for tight loops generating millions of bounded values, pre-creating a Uniform distribution avoids the overhead of range validation on each call. This matters in simulation code, game loops, and high-performance sampling scenarios where gen_range() would repeatedly construct and validate identical bounds.