I’ve recently been playing around with the Rust programming language, and thought it’d be fun to put together a simple program that generates a random password.

As a fairly new Rustacean, I spent quite a bit of time on docs.rs. While poking around in the rand crate, I found a struct named Alphanumeric that would makes generating a random password super easy…but…because I’m trying to gain more experience in Rust, I’m decided to not to take this approach.

Project Setup

First, we’ll set the project up using cargo. I like to build and run the cargo boilerplate (which is a simple “Hello, world!” program) to make sure everything is good to go:

$ cargo new random_password
$ cd random_password
$ cargo run
Hello, world!

We’re going to need the rand crate, so let’s add that to the Cargo.toml now:

[dependencies]
rand = "0.8.4"

Random Password

Now we can open up src/main.rs and start writing the main logic (now’s a good time to delete the “Hello, world!” template). Inside main(), we can start by creating a list of characters that we’ll use to generate the random password:

// I'm using the `concat!` macro to 
// avoid overly long lines
let chars: String = concat!(
    "ABCDEFGHIJKLMNOPQRSTUVWXYZ",
    "abcdefghijklmnopqrstuvwxyz",
    "0123456789",
    "!@#$%&*").to_string();

// take the big string of characters
// and convert it to an array of bytes
let charset: &[u8] = &chars.into_bytes();

Now that we have the charset defined, we’ll need a function to grab a random character from the character array. I’m going to define this above the main() function:

fn get_random_char(charset: &[u8]) -> char {
    let idx = thread_rng().gen_range(0..charset.len());
    
    // the last statement of a Rust function (without
    // a semicolon) is the return value
    charset[idx] as char
}

Notice we are using thread_rng() function to generate a random number, which means we’ll need to add a use statement at the top of the file:

use rand::{Rng, thread_rng};

With our function in place, we can generate a random string of characters. Here’s what we’ll do:

  • Define length as the number of characters in the password
  • Take a range from 0 to length
  • Map the range to the get_random_char() function
let length: usize = 32;

let pass: String = (0..length)
    .map(|_| get_random_char(&charset))
    .collect();

println!("{}", pass);

Great! Let’s give this a run and see if it works:

$ cargo run
Tcq4om%D&Ht9rdYW&2KNafSi4DoIcrrC

Add Command Line Args

That’s a great start, but let’s add some extra functionality. It would be nice if we could dynamically set the length variable via a command line argument. Let’s give it a shot.

First, we’ll need to add the std::env module to read the script arguments. Let’s add that to the top of the script:

use std::env;

Next, we’ll want to keep some sort of default value for the length so that the length argument is optional. I’ll set this as const above the function definitions:

const DEFAULT_LENGTH: usize = 32;

Now let’s initialize the length and the args vector. This can go somewhere in the main() function (but before the pass expression):

let length: usize;
let args: Vec<String> = env::args().collect();

Perfect, now we just need to parse the arguments to see if a length arg was passed to the script. There are probably more robust ways to parse args, but for this use-case a simple match statement will work just fine. It’s important to remember that the script name is the first argument, so we’re looking for cases when there are two args (the script name and the length):

length = match args.len() {
    // if we have 2 args, then enter another
    // match that attempts to parse arg to 
    // an number. if that fails, just use 
    // the default value
    2 => match args[1].trim().parse() {
        Ok(num) => num,
        Err(_) => DEFAULT_LENGTH
    },

    // for everything else, use the default
    _ => DEFAULT_LENGTH,
};

Since the pass expression is already using length, that’s all we need do! Let’s give it a try:

$ cargo build
$ ./target/debug/random_password     # use the default
fe34ubt6JfMxTUNR@iUAK7TJtroiuuuT
$ ./target/debug/random_password 10  # 10 chars
Y2f8ByeLLh

Looks good! Checkout the full code listing below

Full Listing

use std::env;
use rand::{Rng, thread_rng};

const DEFAULT_LENGTH: usize = 32;

fn get_random_char(charset: &[u8]) -> char {
    let idx = thread_rng().gen_range(0..charset.len());
    charset[idx] as char
}

fn main() {
    let characters: String = concat!(
        "ABCDEFGHIJKLMNOPQRSTUVWXYZ",
        "abcdefghijklmnopqrstuvwxyz",
        "0123456789",
        "!@#$%&*").to_string();
    let charset: &[u8] = &characters.into_bytes();

    let length: usize;
    let args: Vec<String> = env::args().collect();
    
    length = match args.len() {
        2 => match args[1].trim().parse() {
            Ok(num) => num,
            Err(_) => DEFAULT_LENGTH
        },
        _ => DEFAULT_LENGTH,
    };

    let pass: String = (0..length)
        .map(|_| get_random_char(&charset))
        .collect();
    println!("{}", pass);
}

Tags:

Updated: