Ship a Password Generator Crate via Cargo 🦀

Tensor Ashish
6 min readJan 25, 2023

--

Cargo is the package manager for Rust, and it is used to manage dependencies and build projects. To get started with Cargo, you will first need to have Rust installed on your computer. You can download and install Rust from the official Rust website (https://www.rust-lang.org/).

We will create a “Password Generator” in Rust and learn about the language and get familiar with its built-in libraries for working with strings and random numbers using Cargo.

Cargo is the Rust package manager.

Once you have Rust installed, you can use the following command to create a new project using Cargo:

cargo new password-generator

Open the “password-generator” directory in your text editor of your choice. At this point the contents of your directory should look similar to:

This is all we need to get started. Lets look at Cargo.toml:

[package]
name = "password-generator"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]

This is called a manifest, and it contains all of the metadata that Cargo needs to compile your package.

Here’s what’s in src/main.rs:

fn main() {
println!("Hello, world!");
}

Cargo: Build it

Cargo generated a “hello world” program for us, otherwise known as a binary crate. Let’s compile it:

cargo build

And then run it:

$ ./target/debug/password-generator
Hello, world!

For windows:

> ./target/debug/password-generator.exe
Hello, world!

Cargo:Run it

Thecargo run command allows you to compile and then run it, all in one step:

cargo run
Finished dev [unoptimized + debuginfo] target(s) in 0.01s
Running `target\debug\password-generator.exe`
Hello, world!

CARGO

Cargo is the package manager for Rust, it is designed to manage dependencies and build projects. It streamlines the process of building and managing Rust projects by introducing two metadata files with package information, fetching and building dependencies, invoking build tools with the correct parameters, and introducing conventions to make working with Rust packages easier. Rather than invoking build tools directly, cargo can be used to handle building different artifacts, regardless of their names, and automatically fetching dependencies from a registry and incorporating them into the build. This makes Cargo an essential tool for building and managing Rust projects efficiently.

Rather than invoke rustc directly, we can instead invoke something generic such as cargo build and let cargo worry about constructing the correct rustc invocation. Furthermore, Cargo will automatically fetch from a registry any dependencies we have defined for our artifact, and arrange for them to be incorporated into our build as needed.

Cargo stores the output of a build into the “target” directory. By default, this is the directory named target in the root of your workspace. To change the location, you can set the CARGO_TARGET_DIR environment variable, the build.target-dir config value, or the --target-dir command-line flag.

The directory layout depends on whether or not you are using the --target flag to build for a specific platform. If --target is not specified, Cargo runs in a mode where it builds for the host architecture. The output goes into the root of the target directory, with each profile stored in a separate subdirectory:

target/debug/Contains output for the dev profile.
target/release/Contains output for the release profile (with the --release option).
target/foo/Contains build output for the foo profile (with the --profile=foo option).

For historical reasons, the dev and test profiles are stored in the debug directory, and the release and bench profiles are stored in the release directory. User-defined profiles are stored in a directory with the same name as the profile.

When building for another target with --target, the output is placed in a directory with the name of the target:

target/<triple>/debug/ => target/thumbv7em-none-eabihf/debug/target/<triple>/release/ => target/thumbv7em-none-eabihf/release/

Dependencies

crates.io is the Rust community’s central package registry that serves as a location to discover and download packages. cargo is configured to use it by default to find requested packages.

To depend on a library hosted on crates.io, add it to your Cargo.toml.

Adding a dependency

If your Cargo.toml doesn't already have a [dependencies] section, add that, then list the crate name and version that you would like to use. This example adds a dependency of the time crate:

[dependencies]
time = "0.1.12"

The version string is a SemVer version requirement. The specifying dependencies docs have more information about the options you have here.

Run cargo build, and Cargo will fetch the new dependencies and all of their dependencies, compile them all, and update the Cargo.lock.

Package Layout

In Rust, the layout of a package is the structure and organization of the files and directories within a package. A package is a unit of code distribution in Rust, it can be a library or an executable and is the fundamental unit of versioning and dependency management in Rust.

The basic layout of a Rust package includes the following elements:

  • src directory: This directory contains the source code for the package. The main entry point for an executable package is the main.rs file, and for a library package is the lib.rs file.
  • Cargo.toml file: This file is the package's manifest file, which contains information about the package such as its name, version, authors, and dependencies.
[package]
name = "password-generator"
version = "0.1.0"
edition = "2021"
  • tests directory: This directory contains test files for the package. Test files have the .rs extension and are located in the tests directory.
  • examples directory: This directory contains examples of how to use the package. Example files have the .rs extension and are located in the examples directory.
  • benches directory: This directory contains benchmarks for the package. Benchmark files have the .rs extension and are located in the benches directory.
  • target directory: This directory contains the files generated by

The official docs for package layout are available here: https://doc.rust-lang.org/cargo/guide/project-layout.html

The Password Generator

Lets work with “main.rs” file, add the following code to the “main.rs” file to import the necessary libraries and define the main function:

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

fn main() {
// Your code here
}

In the main function, ask the user for the desired length of the password and store it in a variable:

 println!("Enter the desired length of the password:");
let mut password_length = String::new();
io::stdin().read_line(&mut password_length).unwrap();
let password_length = password_length.trim().parse::<usize>().unwrap();

Next, create a string that contains all the possible characters for the password. You can include letters, numbers, and special characters.

let possible_chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()_+-=[]{}|;':\",.<>/?`~\\";

Before we use rand, we will need to import the rand crate. Add the crate as dependency open and add `rand=”0.7"` under [dependencies] inCargo.toml:

[dependencies]
rand = "0.7"

Next, using the rand::Rng trait, generate a random password of the desired length using the possible characters:

    let mut rng = thread_rng();
let password: String = (0..password_length).map(|_| {
let idx = rng.gen_range(0, possible_chars.len());
possible_chars.chars().nth(idx).unwrap()
}).collect();

Let us understand the snippet above:

  • let mut rng = thread_rng();: This line creates a new random number generator (RNG) object, rng, using the thread_rng function from the rand crate. The thread_rng function returns a new thread-local random number generator that is suitable for most use cases. The mut keyword is used to indicate that rng is a mutable variable, which means that its value can be changed.
  • (0..password_length): This creates an iterator that generates a range of numbers from 0 to password_length.
  • .map(|_| {...}): The map function applies a given closure (anonymous function) to each element of the iterator, in this case, the number range. In this case, the closure takes an argument _ which is not used and returns a character.
  • let idx = rng.gen_range(0, possible_chars.len());: This line generates a random number between 0 and the length of the possible_chars string (not including the length). It does this by calling the gen_range method on rng.
  • possible_chars.chars().nth(idx).unwrap(): This line gets the nth character from the possible_chars string. It does this by calling the chars method on the string, which returns an iterator over the characters of the string, and then the nth method is used to get the character at index idx. The unwrap method is used to return the character or panics if the index is out of range.
  • .collect(): This function takes the iterator returned by the map function and collects it into a container (in this case, a String)

Print the generated password to the terminal:

    println!("Generated password: {}", password);

Finally, run your project by using the following command:

cargo run
Running `target\debug\password-generator.exe`
Enter the desired length of the password:
8
Generated password: Pcl@bP*5

--

--