---
title: "7.9 Adding a dependency: random numbers"
description: "PRNGs and seeds, crates.io, cargo add, semantic versioning, and rolling dice with the rand crate."
url: "https://learnrust.net/chapter-7/adding-a-dependency-random-numbers/"
last_updated: "2026-06-12"
---

# 7.9 Adding a dependency: random numbers

This chapter's project is a guessing game, and a guessing game needs a secret number that's different every run. That one requirement is going to teach you two things: how computers fake randomness, and how to use code you didn't write, which may quietly be the more important lesson in this chapter.

## Act one: computers can't do random

A computer is a deterministic machine; same program, same inputs, same result, every time (that's most of what we like about them). Dice and coin flips get their unpredictability from physics. Your CPU has no dice. What it can do is *simulate* the look of randomness with arithmetic.

A **pseudo-random number generator** (PRNG) is an algorithm that keeps a number as internal state and, each time you ask, scrambles that state thoroughly and serves you a piece of the result. Here's a tiny, working one, built from nothing but lesson [4.4](https://learnrust.net/chapter-4/integer-overflow/)'s wrapping arithmetic:

```rust
fn main() {
    let mut state: u32 = 7;   // the seed
    for _ in 0..8 {
        state = state.wrapping_mul(747796405).wrapping_add(2891336453);
        print!("{} ", state % 100);
    }
    println!();
}
```

```
92 97 10 7 84 29 18 75 
```

(The `_` is lesson [7.3](https://learnrust.net/chapter-7/introduction-to-match/)'s wildcard doing loop duty: we want eight repetitions and don't care which one is which.) Multiply by a big odd constant, add another, let the result wrap, repeat: the output hops around with no obvious rhyme. But run the program again and you'll get *exactly* the same eight numbers, because of course you will; it's arithmetic.

The starting value of the state is called the **seed**, and it's the only source of variety a PRNG has. Same seed, same sequence, forever (change the 7 above to an 8 and you get `1 6 3 80 41 46 91 64`, also forever). So programs that want different numbers each run seed the generator with something that differs each run, typically entropy the operating system collects. And programs that want *reproducible* randomness, like a game with shareable level codes or a test you can re-run, seed with a fixed value on purpose. Same trick, both directions.

Real PRNGs differ from our toy in quality, not kind: bigger state, better scrambling, statistical guarantees, and immunity to the toy's flaws (low bits repeating in patterns, some values arriving too often). Writing a good one is research-grade work, which is precisely why you shouldn't: this is the textbook moment for *someone else's code*.

## Act two: crates.io and your first dependency

Rust's standard library deliberately doesn't include a random number generator. That sounds like an omission until you remember what's behind it. Back in lesson [0.5](https://learnrust.net/chapter-0/the-compiler-cargo-and-crates/), you learned that shareable units of Rust code are called crates, that the community's crates live at crates.io, and that declaring a **dependency** makes Cargo do all the fetching and compiling; that lesson promised you'd "do it for real in lesson 7.9, adding random numbers to your first game." Welcome to lesson 7.9. The de facto standard crate for randomness is `rand`.

From inside your project's folder, one command:

```bash
$ cargo add rand
    Updating crates.io index
      Adding rand v0.10.1 to dependencies
```

That's it. (The real output is a little chattier: a list of rand's optional "features" with plus and minus signs, and a line about locking package versions. Both are Cargo being thorough; neither needs your attention yet.) Open `Cargo.toml` and the `[dependencies]` section that's sat empty since lesson [0.8](https://learnrust.net/chapter-0/compiling-your-first-program/) (which predicted this moment too) has its first entry:

```toml
[dependencies]
rand = "0.10.1"
```

You could have typed that line by hand (`cargo add` is a convenience, not magic), and on the next build, Cargo does the rest: downloads `rand` and the few crates *it* depends on, compiles them once, and links them into your program. The first build after adding a dependency is noticeably chattier and slower; after that, the compiled results are reused and builds are quick again.

> **What does the version number promise?**
>
> `"0.10.1"` is a **semantic version**: the convention that version numbers encode compatibility. Cargo reads it as "0.10.1 or any newer release that won't break my code" (0.10.2 yes, 0.11 no; a breaking change must bump the leftmost nonzero part of the version, here the 10). This is how thousands of strangers' codebases update each other safely. The first build also writes a `Cargo.lock` file recording the *exact* versions used, so a teammate (or you, next year) building the same project gets byte-identical dependencies. You'll never edit the lockfile; just know it's why builds are reproducible.

## Rolling dice

With `rand` added, the dice roll is one line:

```rust
fn main() {
    let roll = rand::random_range(1..=6);
    println!("You rolled: {roll}");
}
```

```
You rolled: 4
```

(Your roll will differ. That's the feature.) The function takes a range, lesson [7.5](https://learnrust.net/chapter-7/for-loops-and-ranges/)'s syntax in a new home, and returns a uniformly distributed value from it: every face of the die equally likely, none of our toy generator's biases. Behind the scenes, it uses a generator that the operating system seeded for you at startup.

`rand::random_range` is the convenience form, perfect for a roll here and there. When you're generating lots of numbers, ask for the generator once and reuse it:

```rust
use rand::prelude::*;

fn main() {
    let mut rng = rand::rng();
    for _ in 0..10 {
        print!("{} ", rng.random_range(1..=6));
    }
    println!();
}
```

```
5 1 5 6 1 2 2 2 5 4 
```

`rand::rng()` hands you the generator itself; the `use rand::prelude::*;` line imports the crate's commonly-needed names, including the one that puts the `.random_range(...)` method on it (chapter 16 explains how methods can arrive via imports; until then, the prelude line is the recipe). And notice `mut`: act one is hiding in that keyword. Every call scrambles the generator's internal state, and Rust makes the mutation visible in the type system, even when the state belongs to a library.

> **Older tutorials look different**
>
> If you search the web for Rust randomness, you'll find `rand::thread_rng().gen_range(...)` everywhere, including, as of this writing, the official Rust Book's guessing game chapter. Those names are from rand 0.8 and were renamed in 2025 (`thread_rng` → `rng`, `gen_range` → `random_range`). If a tutorial shows the old names, it predates the rename; the concepts translate directly.

> **Reproducible randomness**
>
> For the same-seed-same-sequence trick with a real generator: `StdRng::seed_from_u64(42)` (with the prelude imported) gives you a generator you seed yourself, and then `.random_range(...)` works as usual. Every run produces the same sequence (seed 42, five d6 rolls: `1 4 2 4 6`, every time), which is exactly what you want for debugging a randomness-dependent bug, and exactly what you don't want for a guessing game. Note the reproducibility is per-version: `rand` reserves the right to change `StdRng`'s algorithm across releases.

## Quiz time

**Question #1**

Your friend runs the toy PRNG program from act one on their machine and gets `92 97 10 7 84 29 18 75`, the same numbers as the lesson. Should they file a bug report?

<details class="solution">
<summary>Show solution</summary>

No; this is a PRNG behaving exactly as defined. The sequence is pure arithmetic on the seed, and the seed is hardcoded to 7, so every machine on Earth produces those numbers. If they want different output per run, they need a different seed per run (which is what `rand` arranges automatically), not better luck.

</details>

**Question #2**

Without running it, what can you say for certain about this program's two output lines?

```rust
use rand::prelude::*;

fn main() {
    let mut a = StdRng::seed_from_u64(99);
    let mut b = StdRng::seed_from_u64(99);
    println!("{} {} {}", a.random_range(1..=100), a.random_range(1..=100), a.random_range(1..=100));
    println!("{} {} {}", b.random_range(1..=100), b.random_range(1..=100), b.random_range(1..=100));
}
```

<details class="solution">
<summary>Show solution</summary>

The two lines are identical (and each contains three numbers between 1 and 100). You can't predict *which* numbers without running it, but `a` and `b` are independent generators started from the same seed, so they produce the same sequence in lockstep. (With seed 99 on rand 0.10, both lines read `99 64 76`, and your machine will agree; the 99 leading is pure coincidence.) One generator asked six times would *not* print two matching lines; each call advances the shared state.

</details>

**Question #3**

Write a program that rolls a six-sided die 100 times and reports how many sixes came up. (For loop, one reused generator, a counter. Run it a few times; the answer should hover near 100/6 ≈ 17.)

<details class="solution">
<summary>Show solution</summary>

```rust
use rand::prelude::*;

fn main() {
    let mut rng = rand::rng();
    let mut sixes = 0;
    for _ in 0..100 {
        if rng.random_range(1..=6) == 6 {
            sixes += 1;
        }
    }
    println!("rolled {sixes} sixes out of 100");
}
```

```
rolled 14 sixes out of 100
```

(One sample run; yours will wobble around 17.) If you got exactly 17 every time, you'd be right to get suspicious; randomness that's too tidy isn't random. A result far outside roughly 8 to 27, run after run, would also be suspicious, but that's a statistics course's territory.

</details>

You now have everything the project needs: loops, `match`, input, parsing, and a secret number. Next lesson, the course stops doing examples and builds a program.

## Sitemap

See the full [sitemap](https://learnrust.net/sitemap.md) for all pages.
