← thecodex.expert · The Codex Family of Knowledge
Tier 0 · Absolute Beginner · Rust Project

Password Generator

Generate a random password from a configurable character pool. Teaches Rust's stance on randomness, byte slices, and building a String from parts.

🧠 Teaches how to think spoonfed, every age Last verified:

1 The Problem

We want a tool that creates a strong password: a random mix of letters, numbers, and symbols, of a length the user chooses. It teaches building a string from random choices — and an important lesson about which randomness is safe for security.

Where this shows up: password managers, generating API keys, session tokens, temporary access codes, unique IDs. Any time software needs something unpredictable that an attacker cannot guess.

2 How to Think About It

A password generator is really two decisions: which characters are allowed, and how many of them to pick at random.

The plan — in plain English
1. Build the pool of allowed characters from the requested categories. → 2. Pick one random character from the pool, length times. → 3. Collect the picks into a String.

Build character pool

Ask for length

Pick that many random chars

Join into a password

Show the password

3 The Build — explained part by part

Here is the complete generator. Read the callout below before trusting this for anything real — it is deliberately honest about a sandbox limitation rather than pretending otherwise.

Rustsrc/main.rs
use std::collections::hash_map::RandomState;
use std::env;
use std::hash::{BuildHasher, Hasher};

const LOWER: &[u8] = b"abcdefghijklmnopqrstuvwxyz";
const UPPER: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ";
const DIGITS: &[u8] = b"0123456789";
const SYMBOLS: &[u8] = b"!@#$%^&*()-_=+";

/// A NON-CRYPTOGRAPHIC source of randomness, used here only because this
/// build environment cannot fetch external crates. `RandomState` reseeds
/// itself from the OS's random source on every call and is enough to make a
/// demo password generator behave differently each run, but it is not the
/// tool for a real one. Go's version of this project used `crypto/rand`
/// specifically because password generation is security-sensitive; the
/// direct Rust equivalent is the `rand` crate's `rngs::OsRng` (`cargo add
/// rand`, then `let mut rng = rand::rngs::OsRng;`) — a real project should
/// use that, not this.
fn random_index(len: usize) -> usize {
    let n = RandomState::new().build_hasher().finish();
    (n % len as u64) as usize
}

/// Builds the character pool to draw from, based on which categories are
/// requested. Returns `None` if every category was turned off — including
/// lowercase, which is why it is a flag here too rather than always-on.
fn build_pool(use_lower: bool, use_upper: bool, use_digits: bool, use_symbols: bool) -> Option<Vec<u8>> {
    let mut pool = Vec::new();
    if use_lower {
        pool.extend_from_slice(LOWER);
    }
    if use_upper {
        pool.extend_from_slice(UPPER);
    }
    if use_digits {
        pool.extend_from_slice(DIGITS);
    }
    if use_symbols {
        pool.extend_from_slice(SYMBOLS);
    }
    if pool.is_empty() {
        None
    } else {
        Some(pool)
    }
}

fn generate(length: usize, pool: &[u8]) -> String {
    (0..length)
        .map(|_| pool[random_index(pool.len())] as char)
        .collect()
}

fn main() {
    let length: usize = env::args()
        .nth(1)
        .and_then(|s| s.parse().ok())
        .unwrap_or(16);

    let pool = match build_pool(true, true, true, true) {
        Some(p) => p,
        None => {
            eprintln!("No character categories selected.");
            return;
        }
    };

    println!("{}", generate(length, &pool));
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn generates_a_password_of_the_requested_length() {
        let pool = build_pool(true, true, true, true).unwrap();
        let pw = generate(20, &pool);
        assert_eq!(pw.chars().count(), 20);
    }

    #[test]
    fn only_uses_characters_from_the_pool() {
        let pool = build_pool(true, false, true, false).unwrap(); // lower + digits only
        let pw = generate(200, &pool);
        assert!(pw.chars().all(|c| c.is_ascii_lowercase() || c.is_ascii_digit()));
    }

    #[test]
    fn empty_pool_is_rejected_when_every_category_is_off() {
        assert!(build_pool(false, false, false, false).is_none());
    }

    #[test]
    fn random_index_stays_in_bounds() {
        for _ in 0..500 {
            let i = random_index(10);
            assert!(i < 10);
        }
    }
}
⚠ No in-browser playground here
Rust compiles to a real binary, so unlike the Python version of this project there is no editor above you can run in the browser. Copy the code below and run it on your own machine — it takes seconds once Rust (via rustup) is installed.
What each part does — in plain words
const LOWER / UPPER / DIGITS / SYMBOLS: &[u8] — each pool is a byte-string literal (b"..."), a slice of raw ASCII bytes rather than a &str, because we want to pick individual bytes by index and cast them back to char.

build_pool(use_lower, use_upper, use_digits, use_symbols) -> Option<Vec<u8>> — returns None if every category is turned off, which forces main to handle the “no characters to choose from” case explicitly instead of generating an empty or garbage password.

(0..length).map(|_| pool[random_index(pool.len())] as char).collect() — a range iterator run length times, each iteration picking one random byte from the pool and casting it to char; .collect() gathers the resulting chars straight into a String because Rust infers the target type from the function’s return type.
Common mistakes — and how to avoid them
✗ Treating RandomState-derived randomness as cryptographically secure — it is a convenient stand-in for this sandbox, not a replacement for a real CSPRNG.
✓ In a real project, generate passwords with the rand crate’s OsRng, which wraps the operating system’s cryptographic random source — exactly the distinction Go’s version of this project draws between crypto/rand and math/rand.
✗ Always including the lowercase pool unconditionally — an earlier draft of this exact function did this, and cargo test caught it immediately: a test asserting an all-categories-off pool is empty failed, because lowercase was still being added.
✓ Give every category, including lowercase, its own on/off flag, and let the test suite prove the empty case actually returns None.

4 Test & Prove Each Part

We test that the pool logic and the generated output actually obey the rules they claim to.

A generated password has exactly the requested length
A password built from a restricted pool only contains characters from that pool
Turning off every category is rejected instead of silently returning an empty pool
The random index generator always stays within the pool's bounds
Rustsrc/main.rs (tests module)
#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn generates_a_password_of_the_requested_length() {
        let pool = build_pool(true, true, true, true).unwrap();
        let pw = generate(20, &pool);
        assert_eq!(pw.chars().count(), 20);
    }

    #[test]
    fn only_uses_characters_from_the_pool() {
        let pool = build_pool(true, false, true, false).unwrap(); // lower + digits only
        let pw = generate(200, &pool);
        assert!(pw.chars().all(|c| c.is_ascii_lowercase() || c.is_ascii_digit()));
    }

    #[test]
    fn empty_pool_is_rejected_when_every_category_is_off() {
        assert!(build_pool(false, false, false, false).is_none());
    }

    #[test]
    fn random_index_stays_in_bounds() {
        for _ in 0..500 {
            let i = random_index(10);
            assert!(i < 10);
        }
    }
}

Run with cargo test. This project’s own history is worth reading here: the first version of build_pool always added lowercase letters regardless of its flags, so the “every category off” test failed loudly instead of the bug slipping through — exactly what tests are for.

5 The Interface

INPUTINPUTdesired length
What it expects
16 (as a command-line argument; defaults to 16)
OUTPUTOUTPUTgenerated password
What it returns
L9$Ejr%GjxNx!80C

6 Run It & Automate It

Save the code as src/main.rs inside a Cargo project's src/ folder and run it with cargo run — Cargo compiles and executes in one step while you are experimenting, then cargo build --release gives you an optimized binary once you are done.

Run it locally
cargo run -- 20
Prints one password of the requested length to stdout.

A CI tool like Jenkins runs cargo test automatically whenever the code changes — every line below has a plain explanation.

What you should see when it works
Terminala real run
$ cargo run -- 16
L9$Ejr%GjxNx!80C
If it breaks — how to fix it
🚨 No character categories selected.
This only happens if you edit main to pass all four flags as false. Restore at least one true flag in the build_pool call.
🚨 The password is shorter than expected.
Remember generate counts bytes from an ASCII pool, so this should not happen with the pools defined here — if you add non-ASCII symbols to a pool, cast carefully, since a multi-byte character would break the simple byte-to-char cast used here.
GroovyJenkinsfile
// Jenkinsfile — runs the tests automatically every time the code changes.
pipeline {
    agent any                                  // run on any available machine

    stages {
        stage('Get the code') {
            steps { checkout scm }             // download the latest code
        }
        stage('Set up Rust') {
            steps {
                sh 'rustc --version'                // confirm Rust is installed
                sh 'cargo build'                     // compile, downloading any crates
            }
        }
        stage('Run the tests') {
            steps {
                sh 'cargo clippy -- -D warnings'     // catch obvious mistakes before running
                sh 'cargo test'                       // run every test, show each result
            }
        }
    }

    post {
        success { echo 'All tests passed.' }
        failure { echo 'A test failed — look above.' }
    }
}
🎯 Try this next — make it yours
  1. Use the real rand crate. If you have network access, cargo add rand and generate with rand::rngs::OsRng. (Teaches: the actual production-grade approach.)
  2. Guarantee category coverage. Force at least one uppercase letter, digit, and symbol into every password. (Teaches: mixing guaranteed picks with random ones.)
  3. Add a strength estimate. Report roughly how many bits of entropy the password has, based on pool size and length. (Teaches: a bit of information theory.)
What you learned
You learned Rust’s deliberate separation of general randomness from cryptographic randomness, how to build and index into a byte-slice pool, and watched a real test catch a real logic bug (an always-on lowercase pool) before it shipped. Related: Collections, Testing.