Loading pageâŚ
Rust walkthroughs
Loading pageâŚ
rand::Rng::gen_range handle exclusive vs inclusive bounds for random integer generation?gen_range uses Rust's Range types to determine bound inclusivity: start..end specifies an exclusive upper bound (the value is never returned), while start..=end specifies an inclusive upper bound (the value may be returned). This follows standard Rust range semantics and affects the distribution of possible outputsâfor integers, gen_range(0..10) can return values 0 through 9, while gen_range(0..=10) can return values 0 through 10.
use rand::Rng;
fn main() {
let mut rng = rand::thread_rng();
// Exclusive upper bound: 0..10
// Possible values: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9
let exclusive: i32 = rng.gen_range(0..10);
println!("Exclusive (0..10): {}", exclusive);
// Inclusive upper bound: 0..=10
// Possible values: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10
let inclusive: i32 = rng.gen_range(0..=10);
println!("Inclusive (0..=10): {}", inclusive);
// Both follow standard Rust range semantics
// 0..10 = { x | 0 <= x < 10 } -- exclusive
// 0..=10 = { x | 0 <= x <= 10 } -- inclusive
}The range syntax directly controls whether the upper bound is included in possible outputs.
use rand::Rng;
fn main() {
let mut rng = rand::thread_rng();
// std::ops::Range<T> -- Exclusive
// Created with start..end syntax
let range: std::ops::Range<i32> = 0..10;
let value: i32 = rng.gen_range(range);
// value will be in [0, 10) -- 0 through 9
// std::ops::RangeInclusive<T> -- Inclusive
// Created with start..=end syntax
let range_inclusive: std::ops::RangeInclusive<i32> = 0..=10;
let value: i32 = rng.gen_range(range_inclusive);
// value will be in [0, 10] -- 0 through 10
// Both are supported by gen_range because they
// implement the SampleRange trait
}gen_range accepts any type implementing SampleRange, which includes both Range and RangeInclusive.
use rand::Rng;
fn main() {
let mut rng = rand::thread_rng();
// Exclusive range: 10 possible values
// gen_range(0..10) returns uniformly from {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
// Probability of each value: 1/10
// Inclusive range: 11 possible values
// gen_range(0..=10) returns uniformly from {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
// Probability of each value: 1/11
// The distribution is uniform within the range
// Both generate values with equal probability
// For counting: exclusive range has (end - start) possible values
// inclusive range has (end - start + 1) possible values
}Both ranges produce uniform distributions; inclusive ranges simply include one more possible value.
use rand::Rng;
fn main() {
let mut rng = rand::thread_rng();
// Array indexing: Use exclusive (array length is exclusive upper bound)
let arr = [1, 2, 3, 4, 5];
let index: usize = rng.gen_range(0..arr.len()); // 0..5
println!("Random element: {}", arr[index]);
// Dice roll: Use inclusive (dice show 1-6)
let dice_roll: u8 = rng.gen_range(1..=6);
println!("Dice roll: {}", dice_roll);
// Card in deck: Use exclusive (52 cards, indices 0-51)
let card_index: usize = rng.gen_range(0..52);
println!("Card index: {}", card_index);
// Percentage (0-100%): Use inclusive
let percentage: u8 = rng.gen_range(0..=100);
println!("Percentage: {}%", percentage);
// ASCII letters A-Z (65-90): Use inclusive
let letter_code: u8 = rng.gen_range(65..=90);
let letter = letter_code as char;
println!("Random letter: {}", letter);
// Zero-based ID with specific count: Use exclusive
// IDs 0-999 for 1000 items
let id: u32 = rng.gen_range(0..1000);
}Use exclusive for zero-based indexing and counts; use inclusive when the upper bound is meaningful (dice, percentages).
use rand::Rng;
fn main() {
let mut rng = rand::thread_rng();
// Exclusive range for floats
let f_exclusive: f64 = rng.gen_range(0.0..1.0);
// Returns value in [0.0, 1.0) -- includes 0.0, excludes 1.0
// Inclusive range for floats
let f_inclusive: f64 = rng.gen_range(0.0..=1.0);
// Returns value in [0.0, 1.0] -- includes both endpoints
// For floating point, the difference is subtler due to precision
// 1.0 can be returned in inclusive, but not in exclusive
// Common pattern: random float in [0, 1)
let unit_float: f64 = rng.gen_range(0.0..1.0);
// Random angle in degrees (0-360)
let angle_degrees: f64 = rng.gen_range(0.0..360.0);
// Random angle including exactly 360
let angle_full: f64 = rng.gen_range(0.0..=360.0);
println!("Unit float: {}", unit_float);
println!("Angle: {}", angle_degrees);
}Floating point ranges work the same way; exclusive excludes the upper bound while inclusive includes it.
use rand::Rng;
fn main() {
let mut rng = rand::thread_rng();
// Valid ranges
let valid1: i32 = rng.gen_range(0..10); // OK: start < end
let valid2: i32 = rng.gen_range(0..=10); // OK: start <= end
let valid3: i32 = rng.gen_range(-5..5); // OK: negative start
// Empty range: PANICS at runtime
// let empty: i32 = rng.gen_range(5..5); // Panics! Empty range
// let empty2: i32 = rng.gen_range(5..=4); // Panics! start > end
// For non-panicking version, use gen_range with checked ranges
// or validate before calling
// Single value for inclusive range is valid:
let single: i32 = rng.gen_range(5..=5); // OK: always returns 5
// But for exclusive:
// let single_excl: i32 = rng.gen_range(5..5); // Panics! Empty range
// Always returns 5:
println!("Single value range: {}", single);
}Empty ranges panic at runtime; start..=start is valid but start..start is empty.
use rand::Rng;
fn main() {
// For integers:
// Exclusive range size = end - start
// 0..10 has 10 possible values (0 through 9)
// Distribution: uniform over 10 values
// Inclusive range size = end - start + 1
// 0..=10 has 11 possible values (0 through 10)
// Distribution: uniform over 11 values
// Example distributions:
// gen_range(1..=6) for dice:
// Range: [1, 6], size = 6 - 1 + 1 = 6 values
// Each value has probability 1/6
// gen_range(0..100) for percentage-like:
// Range: [0, 100), size = 100 - 0 = 100 values
// Each value has probability 1/100
// Note: This is NOT percentage (doesn't include 100)
// gen_range(0..=100) for true percentage:
// Range: [0, 100], size = 100 - 0 + 1 = 101 values
// Each value has probability 1/101
// Includes 0%, 1%, ..., 100%
let mut rng = rand::thread_rng();
// When you need exactly N values, use exclusive:
let idx: usize = rng.gen_range(0..N_ITEMS); // Correct
// When the upper bound is a meaningful endpoint, use inclusive:
let port: u16 = rng.gen_range(49152..=65535); // Ephemeral ports
}Use exclusive for counts (N items, indices 0 to N-1); use inclusive for meaningful bounds.
use rand::Rng;
use rand::distributions::{Standard, Distribution, Uniform};
fn main() {
let mut rng = rand::thread_rng();
// gen_range is a convenience method
let value1: i32 = rng.gen_range(0..10);
// Equivalent to using Uniform distribution
let uniform = Uniform::from(0..10);
let value2: i32 = uniform.sample(&mut rng);
// Standard distribution samples from type's full range
// For i32, this is i32::MIN to i32::MAX
let value3: i32 = rng.gen::<i32>(); // Uses Standard
// gen_range is preferred for bounded ranges:
// - More readable
// - Type inference works better
// - Optimized for the common case
// Uniform distribution is useful when:
// - Reusing the same range multiple times
// - Need to inspect the distribution
let dice = Uniform::from(1..=6);
let roll1: u8 = dice.sample(&mut rng);
let roll2: u8 = dice.sample(&mut rng);
let roll3: u8 = dice.sample(&mut rng);
// Slightly more efficient for repeated sampling
// (Range is parsed once, not each call)
}gen_range is a convenience wrapper around Uniform; use Uniform directly for repeated sampling from the same range.
use rand::Rng;
fn main() {
let mut rng = rand::thread_rng();
// gen_range works with any type implementing SampleUniform
// This includes: i8, i16, i32, i64, i128, isize
// u8, u16, u32, u64, u128, usize
// f32, f64
// Duration, Wrapping<T>
// Integer types:
let i: i32 = rng.gen_range(-100..100);
let u: u64 = rng.gen_range(0..u64::MAX);
let s: isize = rng.gen_range(0..1000);
// Float types:
let f: f32 = rng.gen_range(0.0f32..1.0f32);
let d: f64 = rng.gen_range(-1.0..=1.0);
// Duration:
use std::time::Duration;
let dur: Duration = rng.gen_range(Duration::from_secs(1)..=Duration::from_secs(10));
// Wrapping (for overflow-semantics):
use std::num::Wrapping;
let wrapped: Wrapping<u8> = rng.gen_range(Wrapping(0)..=Wrapping(255));
// Custom types can implement SampleUniform
}gen_range supports all primitive numeric types plus Duration and Wrapping.
use rand::Rng;
use rand::distributions::Uniform;
fn main() {
// For one-off random values, gen_range is ideal:
let mut rng = rand::thread_rng();
let value: i32 = rng.gen_range(0..1000);
// For many values from the same range, Uniform is slightly better:
let dist = Uniform::from(0..1000);
for _ in 0..1000 {
let value: i32 = dist.sample(&mut rng);
}
// The difference is marginal because:
// - gen_range internally uses Uniform::from(range).sample(rng)
// - Uniform::from is cheap for primitive types
// - The overhead is in the range construction, not sampling
// For small integers, the difference is negligible
// For floats or complex types, Uniform reuse may be noticeable
// Premature optimization note:
// - Readability usually matters more than micro-optimization
// - Use gen_range unless you have measured a need for Uniform
}gen_range is efficient for single values; use Uniform directly for repeated sampling.
use rand::Rng;
fn main() {
let mut rng = rand::thread_rng();
// Range expressions can be stored and reused
let index_range = 0..100;
let idx1: usize = rng.gen_range(index_range.clone());
let idx2: usize = rng.gen_range(index_range);
// Inclusive ranges can be stored too
let dice_range = 1..=6;
let roll1: u8 = rng.gen_range(dice_range.clone());
let roll2: u8 = rng.gen_range(dice_range);
// Note: Range types are Copy for Copy types
// Cloning is automatic for primitive ranges
// Dynamic ranges:
let min = 10;
let max = 20;
let value: i32 = rng.gen_range(min..max); // Exclusive
let value2: i32 = rng.gen_range(min..=max); // Inclusive
// Full type range:
let any_u8: u8 = rng.gen_range(0..=u8::MAX); // Full u8 range
let any_u8_alt: u8 = rng.gen_range(u8::MIN..=u8::MAX);
// Half-open ranges (common in Rust):
let slice_start: usize = rng.gen_range(0..slice.len());
}Ranges can be stored and reused; they implement Copy for primitive types.
use rand::Rng;
fn main() {
let mut rng = rand::thread_rng();
// Mistake 1: Off-by-one with exclusive
// Want: values 1 through 10 inclusive
let wrong: i32 = rng.gen_range(1..10); // Returns 1-9, not 10
let correct: i32 = rng.gen_range(1..=10); // Returns 1-10
// Mistake 2: Off-by-one with array length
let arr = [1, 2, 3, 4, 5];
let correct_idx: usize = rng.gen_range(0..arr.len()); // 0..5, correct
let wrong_idx: usize = rng.gen_range(0..=arr.len()); // 0..=5, can be 5 (out of bounds!)
// Mistake 3: Empty range panic
// let empty: i32 = rng.gen_range(10..10); // Panics!
let single: i32 = rng.gen_range(10..=10); // OK, returns 10
// Mistake 4: Inverted range
// let inverted: i32 = rng.gen_range(10..5); // Panics!
// let inverted2: i32 = rng.gen_range(10..=5); // Panics!
// Correct patterns:
// For N items (0-indexed): use 0..N
// For dice (1-6): use 1..=6
// For percentage: use 0..=100
// For array index: use 0..len()
}Common errors: off-by-one with exclusive ranges, empty ranges, and inverted bounds.
Exclusive range (start..end):
gen_range(0..10) returns 0-9end - startInclusive range (start..=end):
gen_range(0..=10) returns 0-10end - start + 1Key behaviors:
| Range Type | Syntax | Example | Possible Values | Count |
|------------|--------|---------|-----------------|-------|
| Exclusive | 0..10 | gen_range(0..10) | 0, 1, ..., 9 | 10 |
| Inclusive | 0..=10 | gen_range(0..=10) | 0, 1, ..., 10 | 11 |
| Single | 5..=5 | gen_range(5..=5) | 5 | 1 |
| Empty | 5..5 | gen_range(5..5) | Panics! | 0 |
When to use which:
..): Array indices, loop counters, "N values starting from X"..=): Dice, percentages, ranges where the upper bound is meaningful, full type range (0..=MAX)The fundamental insight: gen_range follows Rust's standard range semantics exactlyâRange (exclusive) and RangeInclusive (inclusive) both implement SampleRange, and gen_range samples uniformly from the specified interval. The choice between them is about which bound semantics match your problem domain: use exclusive when the upper bound represents a count or limit, inclusive when the upper bound is a valid value in the range itself.