Rust Random Numbers with rand
Games roll dice. Quizzes shuffle their questions. Tests need sample data. All of these need random numbers, and Rust’s standard library doesn’t have a ready-made random number generator. Instead, almost everyone uses one crate: rand.
This page shows the modern rand 0.10 API. That matters, because rand changed some names in versions 0.9 and 0.10. If an older tutorial shows thread_rng() or gen_range(), it was written for an older version. We’ll see the new names and the errors you get from the old ones.
Adding rand to your project
Section titled “Adding rand to your project”In your project folder, run:
cargo add randThat adds a line like this to your Cargo.toml:
[dependencies]rand = "0.10"If crates and Cargo.toml are new to you, read that page first. The “Try it” buttons on this page open the Rust Playground, which already includes rand.
Quick random values
Section titled “Quick random values”For a one-off random value, use the free functions rand::random_range and rand::random:
fn main() { let die: u32 = rand::random_range(1..=6); let coin: bool = rand::random(); let chance: f64 = rand::random();
println!("die is between 1 and 6: {}", (1..=6).contains(&die)); println!("chance is in 0.0..1.0: {}", (0.0..1.0).contains(&chance)); println!("coin is heads: {coin}");}There’s no output block here on purpose: the last line is true or false at random. Click “Try it” a few times and watch it change.
random_range(1..=6)gives a number from 1 to 6, including 6. With1..6you’d only get 1 to 5. Ranges work the same as in a for loop.random()picks a value of whatever type you ask for. For aboolthat’s a fair coin flip. For anf64it’s a number from0.0up to (but not including)1.0.- The type annotation (
: u32,: bool) tellsrandwhat to make. Without it, Rust may not know which type you want.
A generator and the RngExt trait
Section titled “A generator and the RngExt trait”When you need many random values, grab a generator once with rand::rng() and call methods on it. This is a bit faster, and it’s the style you’ll see in most code:
use rand::RngExt;
fn main() { let mut rng = rand::rng();
let rolls: Vec<u32> = (0..1000).map(|_| rng.random_range(1..=6)).collect(); println!("rolled {} dice", rolls.len()); println!("all between 1 and 6: {}", rolls.iter().all(|r| (1..=6).contains(r)));
let x: f64 = rng.random(); println!("x is in 0.0..1.0: {}", (0.0..1.0).contains(&x));}Output:
rolled 1000 diceall between 1 and 6: truex is in 0.0..1.0: trueWe print checks instead of the numbers themselves, so the output is the same every time even though the values aren’t.
Two more methods you’ll use often:
let lucky = rng.random_bool(0.1); // true about 10% of the timelet rare = rng.random_ratio(1, 100); // true about 1 time in 100Why the use rand::RngExt; line? Methods like random_range and random_bool come from a trait, and you can only call a trait’s methods when the trait is in scope. In rand 0.10 there are two traits:
Rngis the basic “I can produce random bits” trait. Every generator implements it.RngExtadds the friendly methods:random,random_range,random_booland more. It works on everyRngautomatically.
So: import RngExt to call the methods, and use Rng when you write a function that accepts “any generator”. You’ll see both in the dice roller below.
Reproducible results with a seed
Section titled “Reproducible results with a seed”rand::rng() gives different numbers every run. That’s what you want in a real game. But for tests, debugging, or a game’s “daily challenge” level, you want the same “random” numbers every time.
A seed does this. A seeded generator always produces the same sequence for the same seed:
use rand::rngs::StdRng;use rand::{RngExt, SeedableRng};
fn main() { let mut a = StdRng::seed_from_u64(2024); let mut b = StdRng::seed_from_u64(2024);
let first: Vec<u8> = (0..4).map(|_| a.random_range(0..100)).collect(); let second: Vec<u8> = (0..4).map(|_| b.random_range(0..100)).collect();
println!("{:?}", first); println!("{:?}", second); println!("same? {}", first == second);}Output:
[53, 82, 74, 51][53, 82, 74, 51]same? trueseed_from_u64 comes from the SeedableRng trait, so that needs importing too. Change 2024 to another number and you get a different sequence, but still the same one on every run.
One honest warning: StdRng’s algorithm is allowed to change in a future rand release. The numbers above come from rand 0.10. If your tests must give the exact same numbers forever, the rand book recommends a named generator such as ChaCha8Rng (see “Read more” below).
Shuffling and choosing from a slice
Section titled “Shuffling and choosing from a slice”To work with lists, import two more traits from rand::seq:
SliceRandomgivesshuffle, which mixes up a slice in place.IndexedRandomgiveschoose(one random item) andsample(several different items).
use rand::rngs::StdRng;use rand::seq::{IndexedRandom, SliceRandom};use rand::SeedableRng;
fn main() { let mut rng = StdRng::seed_from_u64(7);
let mut cards = vec!["A", "K", "Q", "J", "10"]; cards.shuffle(&mut rng); println!("shuffled: {:?}", cards);
let colors = ["red", "green", "blue"]; println!("picked: {:?}", colors.choose(&mut rng));
let team: Vec<&str> = cards.sample(&mut rng, 2).copied().collect(); println!("team: {:?}", team);
let empty: [i32; 0] = []; println!("from empty: {:?}", empty.choose(&mut rng));}Output:
shuffled: ["K", "J", "10", "Q", "A"]picked: Some("red")team: ["K", "J"]from empty: Nonechoose returns an Option, because an empty slice has nothing to choose. These methods work on arrays, vectors and slices alike. Replace the seeded rng with rand::rng() and you get a new shuffle every run.
Example: a dice roller
Section titled “Example: a dice roller”Let’s write a function that rolls dice. The trick is the parameter type: &mut impl Rng means “any random generator”. In the real game you pass rand::rng(). In a test you pass a seeded StdRng and know exactly what comes out:
use rand::rngs::StdRng;use rand::{Rng, RngExt, SeedableRng};
/// Roll `count` dice with `sides` sides each.fn roll(rng: &mut impl Rng, count: u32, sides: u32) -> Vec<u32> { (0..count).map(|_| rng.random_range(1..=sides)).collect()}
fn main() { let mut rng = StdRng::seed_from_u64(1);
for (count, sides) in [(2, 6), (1, 20), (3, 4)] { let rolls = roll(&mut rng, count, sides); let total: u32 = rolls.iter().sum(); println!("{count}d{sides}: {:?} = {total}", rolls); }}Output:
2d6: [5, 6] = 111d20: [16] = 163d4: [3, 1, 2] = 62d6 is board-game shorthand for “two six-sided dice”. Passing the generator in, instead of calling rand::rng() inside roll, is a good habit: it keeps the function easy to test.
Example: a random password generator
Section titled “Example: a random password generator”Here we pick 16 characters from an allowed set. The set leaves out look-alike characters like 0/O and 1/l, so the password is easier to type:
use rand::seq::IndexedRandom;use rand::Rng;
const CHARSET: &[u8] = b"ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz23456789!@#$%&*";
fn password(rng: &mut impl Rng, length: usize) -> String { (0..length) .map(|_| *CHARSET.choose(rng).unwrap() as char) .collect()}
fn main() { let mut rng = rand::rng(); let pw = password(&mut rng, 16);
println!("length: {}", pw.len()); println!("only allowed characters: {}", pw.bytes().all(|b| CHARSET.contains(&b))); // println!("{pw}"); // uncomment to see it: different on every run}Output:
length: 16only allowed characters: trueb"..." is a byte string: a &[u8] of ASCII characters. choose picks one byte, and as char turns it into a character. Notice we used rand::rng() here, never a seeded generator. A password from a fixed seed is the same password every time, which is no password at all.
Back to the guessing game
Section titled “Back to the guessing game”The Guessing Game project picks its secret number with a clock trick, so it can stay standard-library only. With rand, the whole secret_number function becomes one line:
let secret: u32 = rand::random_range(1..=100);Everything else in the game stays the same. Here’s a version you can run in the Playground. The secret comes from a seeded generator, and the computer guesses by always picking the middle of the remaining range:
use rand::rngs::StdRng;use rand::{RngExt, SeedableRng};use std::cmp::Ordering;
fn main() { // In the real game: let secret: u32 = rand::random_range(1..=100); let mut rng = StdRng::seed_from_u64(99); let secret: u32 = rng.random_range(1..=100);
let (mut low, mut high) = (1, 100); let mut attempts = 0; loop { let guess = (low + high) / 2; attempts += 1; match guess.cmp(&secret) { Ordering::Less => { println!("{guess} is too small"); low = guess + 1; } Ordering::Greater => { println!("{guess} is too big"); high = guess - 1; } Ordering::Equal => { println!("{guess} is right! Found in {attempts} attempts."); break; } } }}Output:
50 is too small75 is too small88 is too small94 is too small97 is too small99 is right! Found in 6 attempts.Fun fact: this “halve the range” strategy never needs more than 7 guesses for 1 to 100.
Is rand safe for passwords and keys?
Section titled “Is rand safe for passwords and keys?”Short answer: rand::rng() is designed to be hard to predict, but for serious secrets, use tools made for the job.
rand::rng()uses a cryptographically secure algorithm (currently ChaCha12). It is seeded from your operating system’s random source, through thegetrandomcrate, and reseeds itself regularly. The rand docs call it a “fast, reasonably secure generator”, and say they can’t promise it fits every security need.- For encryption keys, API tokens, and other high-stakes secrets, get the bytes straight from the operating system with
getrandom(orrand::rngs::SysRng), or better, let a dedicated crypto library generate the key for you. - Never use a seeded generator (
seed_from_u64) or the fastSmallRngfor anything secret. Anyone who knows or guesses the seed can produce the same “random” values.
For games, simulations, shuffling and test data, rand::rng() is exactly right.
Common mistakes
Section titled “Common mistakes”Importing Rng instead of RngExt. In rand 0.9 the methods lived on Rng, so older code does use rand::Rng;. With rand 0.10 that gives:
use rand::Rng;
fn main() { let mut rng = rand::rng(); let n = rng.random_range(1..=6); println!("{n}");}error[E0599]: no method named `random_range` found for struct `ThreadRng` in the current scope --> src/main.rs:5:17 |5 | let n = rng.random_range(1..=6); | ^^^^^^^^^^^^ | = help: items from traits can only be used if the trait is in scopehelp: trait `RngExt` which provides `random_range` is implemented but not in scope; perhaps you want to import it |1 + use rand::RngExt; |The compiler tells you the fix: use rand::RngExt;.
Copying code written for rand 0.8. Very old examples use thread_rng() and gen_range():
fn main() { let n = rand::thread_rng().gen_range(1..=6); println!("{n}");}error[E0425]: cannot find function `thread_rng` in crate `rand` --> src/main.rs:2:19 |2 | let n = rand::thread_rng().gen_range(1..=6); | ^^^^^^^^^^ not found in `rand`The new names are rand::rng() and random_range(). Likewise, gen() became random() and gen_bool() became random_bool().
Off-by-one ranges. random_range(1..6) never returns 6. For a die, write 1..=6.
- Add the crate with
cargo add rand. Rust’s standard library has no random number generator of its own. - For quick values use
rand::random_range(1..=6)andrand::random::<T>(). - For many values, use
let mut rng = rand::rng();withuse rand::RngExt;for methods likerandom_rangeandrandom_bool. SliceRandom::shufflemixes a slice;IndexedRandom::choosepicks one item and returns anOption.StdRng::seed_from_u64(n)gives the same sequence every run: great for tests and repeatable game levels, never for secrets.- For keys and tokens, use the OS random source (
getrandom) or a dedicated crypto crate.
Quick check
Section titled “Quick check”Quick check
1. Which call can return 6?
2. With rand 0.10, rng.random_range(..) fails with "no method named random_range". What is missing?
3. Why would you use StdRng::seed_from_u64(42)?
4. What does choose return when you call it on an empty slice?
Score: 0 / 4
Read more
Section titled “Read more”- The Rust Rand Book: the official guide to
rand, from quick start to choosing a generator. randAPI docs — docs.rs: every function, trait and generator, with examples.- Updating to 0.10 — The Rust Rand Book: what changed in 0.10, including the
Rng/RngExtrename. Useful when you meet older code. - Our RNGs — The Rust Rand Book: compares the generators, including which are suitable for security.
- Reproducibility — The Rust Rand Book: how to keep seeded results stable across versions and platforms.