Building a CLI Tool in Rust
A quick guide to building a fast, ergonomic command-line tool using Rust and clap.
Setup
Create a new project:
cargo new greet-cli
cd greet-cli
Add dependencies to Cargo.toml:
[dependencies]
clap = { version = "4", features = ["derive"] }
anyhow = "1"
Defining the CLI
Use clap's derive macros to define your arguments:
use clap::Parser;
#[derive(Parser)]
#[command(name = "greet", about = "A friendly greeter")]
struct Args {
/// Name of the person to greet
name: String,
/// Number of times to greet
#[arg(short, long, default_value_t = 1)]
count: u8,
/// Use uppercase
#[arg(short, long)]
uppercase: bool,
}
The main function
use anyhow::Result;
fn main() -> Result<()> {
let args = Args::parse();
for _ in 0..args.count {
let greeting = format!("Hello, {}!", args.name);
if args.uppercase {
println!("{}", greeting.to_uppercase());
} else {
println!("{greeting}");
}
}
Ok(())
}
Usage
$ cargo run -- world
Hello, world!
$ cargo run -- --count 3 --uppercase Rust
HELLO, RUST!
HELLO, RUST!
HELLO, RUST!
Error handling
anyhow gives you ergonomic error handling. If you read a file:
use std::fs;
fn read_config(path: &str) -> Result<String> {
let content = fs::read_to_string(path)?;
Ok(content)
}
The ? operator propagates errors with full context. No unwrap, no panic.
Testing
Add tests in the same file:
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn verify_cli() {
use clap::CommandFactory;
Args::command().debug_assert();
}
}
Run them:
cargo test
Distribution
Build a release binary:
cargo build --release
The binary is at target/release/greet. It's a single static file — no runtime, no dependencies. Copy it anywhere and it works.
That's it. A complete CLI in under 50 lines of Rust. Fast to compile, fast to run, and pleasant to maintain.