Ship a Password Generator Crate via Cargo 🦀
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 ascargo build
and let cargo worry about constructing the correctrustc
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
.
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 themain.rs
file, and for a library package is thelib.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 thetests
directory.examples
directory: This directory contains examples of how to use the package. Example files have the.rs
extension and are located in theexamples
directory.benches
directory: This directory contains benchmarks for the package. Benchmark files have the.rs
extension and are located in thebenches
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 thethread_rng
function from therand
crate. Thethread_rng
function returns a new thread-local random number generator that is suitable for most use cases. Themut
keyword is used to indicate thatrng
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 topassword_length
..map(|_| {...})
: Themap
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 thepossible_chars
string (not including the length). It does this by calling thegen_range
method onrng
.possible_chars.chars().nth(idx).unwrap()
: This line gets the nth character from thepossible_chars
string. It does this by calling thechars
method on the string, which returns an iterator over the characters of the string, and then thenth
method is used to get the character at indexidx
. Theunwrap
method is used to return the character or panics if the index is out of range..collect()
: This function takes the iterator returned by themap
function and collects it into a container (in this case, aString
)
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