Introduction
This guide is how I would start a new Rust CLI project. See Getting Good at Getting Started for the philosophy behind this guide.
Here are some aliases you might encounter along the way:
g
is my zsh alias forgit
rustup
rustup is the Rust toolchain installer. It install Rust and various CLI tools. It is also the Rust version manager. It also installs Rust's build system and package manager: cargo.
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
cargo
cargo new
creates a new project. We will call this one app
.
cargo new app
cd app
At this point you have a "hello world" that you can run.
cargo run
#=> "Hello, world!"
This command (in addition to cargo build
) created a binary at ./target/debug/app
. You can run $ ./target/debug/app
to see the same output. The "debug" build is not optimized. The optimized binary build is called a "release". It is stored in ./target/release/app
.
cargo build --release
./target/release/app
#=> "Hello, world!"
cargo install
You can install the binary locally on $PATH
. It will build the release version and install it in a directory that is (probably) already on your $PATH
.
cargo install --path .
which add
#=> (on Mac) /Users/[username]/.cargo/bin/app
add
#=> "Hello, world!"
git and github
cargo new
will implicitly call git init
when creating your project. At this point you can write your first commit, create a github repo, and push.
g commit "First commit."
gh repo create
#=> Github CLI workflow for creating a new repo
clap
clap is one of the most popular CLI libraries in Rust.
cargo add clap --features derive
derive
is a clap optional feature that allows for deriving CLI commands from structs. It is also clap's recommended method for building CLIs. You can learn more via clap's derive tutorial.
clap
stands for "command line argument parser". Parsing command line arguments is what it does. You give it a struct that instructs it on how to parse. In return it builds helpful features, such as a help menu. What you do with your parsed command line arguments is up to your main
function.
use clap::{Parser, Subcommand};
#[derive(Parser)]
#[command(version, about)]
struct Cli {
#[command(subcommand)]
command: Commands,
}
#[derive(Debug, Subcommand)]
enum Commands {
/// Adds numbers
#[command()]
Add {
/// first number
x: i32,
/// second number
y: i32,
},
}
fn main() {
let args = Cli::parse();
match args.command {
Commands::Add { x, y } => {
let sum = x + y;
println!("{x} + {y} = {sum}")
}
}
}
Tests
I don't understand what is happening under the hood here, but clap has a builtin testing apparatus described in derive tutorial chapter 4.
struct Cli {
// ...
}
fn main() {
// ...
}
#[test]
fn verify_cli() {
use clap::CommandFactory;
Cli::command().debug_assert()
}
CICD
You can use github actions to test your Rust CLI remotely. I slightly modify the quickstart cicd workflow from actions-rs, so I'll just link to it and not show my minor modifications.
Documentation
Generate documentation for your package and all of it's dependencies.
cargo doc
It is saved in ./target/doc
and can be opened locally in the browser.
cargo doc --open
Publishing
You can publish your crate to crates.io. See more at The Cargo Book: 3.9. Publishing crates on crates.io
You can alternatively publish on OS package manager. For example, see the docs on adding a formula to brew.
Full disclosure I have not done either of these in general, yet.