---
title: How to Generate Random Numbers in Rust (rand crate)
summary: Generate random numbers in Rust by adding the rand crate and using the gen_range method on a seeded generator.
lang: en
slug: how-to-generate-random-numbers-in-rust-rand-crate
published: 2026-04-17T14:12:14+00:00
updated: 2026-04-25T22:00:13+00:00
canonical: https://www.rustfaq.org/en/how-to-generate-random-numbers-in-rust-rand-crate/
---

# How to Generate Random Numbers in Rust (rand crate)



## The Goblin Needs a Dice Roll

You're building a text adventure. The goblin attacks. You need a damage roll. You reach for a random number function, type `rand()`, and the compiler tells you that function doesn't exist. You check the standard library docs. Nothing. Rust doesn't ship with a random number generator. The standard library stays focused on deterministic, safe systems code. Randomness requires entropy, and entropy requires platform-specific details that the standard library avoids. You need the `rand` crate.

## Why Rust Doesn't Ship with Randomness

The Rust standard library prioritizes predictability. System libraries must behave the same way every time they run. Randomness breaks that rule. Generating high-quality random numbers also involves security considerations. A bad random number generator can leak secrets or crash under load. The Rust ecosystem solves this by pushing randomness into a community-maintained crate. The `rand` crate is the de facto standard. It's fast, safe, and battle-tested. It handles the messy details of entropy collection, algorithm selection, and bias correction so you don't have to.

## The `rand` Crate and the `Rng` Trait

Add `rand` to your `Cargo.toml`. The crate exposes a central trait called `Rng`. This trait defines the interface for all random number generators. Methods like `gen_range` live on the trait, not on the generator structs. This design lets you swap generators without changing your logic. You can write a function that accepts `impl Rng` and test it with a seeded generator while running it with a fast, auto-seeded generator in production.

Think of the `Rng` trait as a universal remote. Different generators are different TV brands. The remote has the same buttons for every brand. You press `gen_range`, and the remote talks to whatever generator you're holding.

## Quick Randomness with `thread_rng`

For most applications, you want `rand::thread_rng()`. This function returns a thread-local random number generator that initializes automatically using OS entropy. It caches the generator per thread, so calling it repeatedly is cheap. The first call pays the entropy cost. Subsequent calls reuse the cached instance.

```rust
use rand::Rng;

fn main() {
    // thread_rng() returns a cached, auto-seeded generator for the current thread.
    // It's the standard choice for application code.
    let mut rng = rand::thread_rng();

    // gen_range takes a Range or RangeInclusive.
    // 1..=6 produces values from 1 to 6 inclusive.
    let dice_roll = rng.gen_range(1..=6);

    println!("You rolled a {}", dice_roll);
}
```

Convention aside: always use `rand::thread_rng()` when you just need a number. Don't construct a `StdRng` manually unless you have a specific reason. The community expects `thread_rng` for general purpose randomness. It's optimized and safe.

Trust `gen_range`. It knows more about statistics than you do.

## The Hidden Trap: Modulo Bias

Beginners often try to roll dice using the modulo operator. This introduces bias. If you generate a random `u32` and take `value % 6`, the results aren't uniform. The `u32` range doesn't divide evenly by 6. Some outcomes appear slightly more often than others. In a game, this means the goblin hits harder more often than the math says.

`gen_range` avoids this trap. It implements rejection sampling. The method generates a raw number, checks if it falls within a range that divides evenly by your target size, and discards values that would skew the result. It retries until it finds a valid value. This guarantees uniform distribution. The performance cost is negligible. The safety is free.

Never roll dice with the percent operator. Use `gen_range`.

## Reproducibility with Seeds

Sometimes you need the same random sequence every time. This happens during testing, debugging, or when you need to verify a simulation. `thread_rng` won't help here. It produces different results on every run. You need `StdRng` with a seed.

`StdRng` is a fast, deterministic generator. You control the starting state via a seed. The same seed always produces the same sequence. Use the `SeedableRng` trait to create a seeded instance.

```rust
use rand::{Rng, SeedableRng};
use rand::rand::rngs::StdRng;

fn simulate_battle(seed: u64) -> u32 {
    // StdRng allows you to set a seed for reproducible results.
    // This is essential for debugging and testing.
    let mut rng = StdRng::seed_from_u64(seed);

    let mut health = 100;
    while health > 0 {
        // Damage varies between 5 and 15.
        let damage = rng.gen_range(5..=15);
        health = health.saturating_sub(damage);
        println!("Hit for {} damage. Health: {}", damage, health);
    }
    0 // Victory
}

fn main() {
    // Running with the same seed produces identical output.
    simulate_battle(12345);
}
```

Convention aside: use `seed_from_u64` for simple seeds. For complex seeding, `SeedableRng` provides `from_entropy` and `from_rng`. Stick to `seed_from_u64` unless you need cryptographic seeding.

Seeds turn chaos into a script. Use them to tame your randomness during development.

## Pitfalls and Compiler Errors

The compiler catches common mistakes. Watch for these patterns.

If you forget to import the `Rng` trait, the compiler rejects your code with `E0599` (no method named `gen_range` found). The `gen_range` method comes from the trait. Add `use rand::Rng` to your scope.

If you declare the generator without `mut`, the compiler rejects you with `E0596` (cannot borrow as mutable). Random number generators mutate their internal state on every call. Always declare `let mut rng`.

Range boundaries trip people up. `1..10` excludes 10. `1..=10` includes 10. Use `..=` for inclusive ranges like dice rolls. Use `..` for half-open ranges like array indices.

Modulo bias is a silent killer. The compiler won't warn you. Your game balance will suffer. Use `gen_range` for all numeric random needs.

Don't fight the compiler here. Import the trait and mark the variable mutable.

## Choosing Your Generator

Pick the tool that matches your need for control. Reproducibility saves hours of debugging.

Use `rand::thread_rng()` when you need a quick random number in application code and don't care about reproducibility. It's fast, cached, and auto-seeded.

Use `StdRng` with a seed when you are writing tests, debugging a simulation, or need deterministic behavior for verification. The same seed always yields the same sequence.

Use `gen_range` for almost all numeric random needs. It handles range boundaries and bias correction automatically. It accepts `Range` and `RangeInclusive`.

Use `random::<T>()` only for quick one-offs where you don't need a specific range, like generating a random boolean or a single byte. It calls `thread_rng().gen::<T>()` under the hood.

Use `slice::shuffle` when you need to randomize the order of a collection rather than generating individual numbers. It takes a generator and shuffles in place.

Reach for plain `gen_range` when lifetimes are simple. The unsafe alternative is rarely worth it.

## Where to go next

- [How to use rand crate in Rust random numbers](/en/how-to-use-rand-crate-in-rust-random-numbers/)
- [Understanding Rust Traits](/en/understanding-rust-traits/)
- [Cargo.toml Basics](/en/cargo-toml-basics/)