Last updated: April 20, 2025
Table of Contents
1. Introduction: Rust for CLIs
Rust is an excellent choice for building command-line interface (CLI) applications. Its performance results in fast startup times and efficient execution, its static typing helps prevent runtime errors, and its robust ecosystem provides great libraries for common CLI tasks. Creating CLIs that are both powerful and user-friendly requires effective parsing of command-line arguments.
While you could parse arguments manually using std::env::args
, this quickly becomes complex and error-prone. This is where argument parsing libraries come in.
2. What is clap
?
clap
(Command Line Argument Parser) is the most popular and feature-rich crate in the Rust ecosystem for parsing command-line arguments. It allows you to define your application's arguments, options, flags, and subcommands declaratively or programmatically.
Key features include:
- Automatic generation of help messages (
--help
or-h
). - Automatic generation of version information (
--version
or-V
) fromCargo.toml
. - Support for subcommands (like
git checkout
orcargo build
). - Type checking and validation for arguments.
- Shell completion generation.
- Multiple ways to define arguments (Derive API, Builder API).
This guide focuses primarily on the recommended and more ergonomic **Derive API**.
3. Setup
To use the Derive API, add clap
to your Cargo.toml
with the derive
feature enabled:
[dependencies]
clap = { version = "4.5", features = ["derive"] } # Check crates.io for the latest version
Or use the command line:
cargo add clap --features derive
4. The Derive API: Declaring Arguments
The Derive API allows you to define your CLI structure using standard Rust structs and enums decorated with attributes.
4.1 Basic Parser Struct
You start by defining a struct and deriving clap::Parser
for it. Attributes on the struct itself configure the overall application behavior.
use clap::Parser;
/// A simple program to greet someone
#[derive(Parser, Debug)]
#[command(version, about, long_about = None)] // Reads version from Cargo.toml, uses doc comment for about
struct Cli {
/// Name of the person to greet
name: String,
/// Number of times to greet
#[arg(short, long, default_value_t = 1)] // Defines -c/--count option
count: u8,
/// Enable verbose logging
#[arg(short, long, action = clap::ArgAction::SetTrue)] // Defines -v/--verbose flag
verbose: bool,
}
fn main() {
let cli = Cli::parse();
println!("Name: {:?}", cli.name);
println!("Count: {}", cli.count);
println!("Verbose: {}", cli.verbose);
for _ in 0..cli.count {
println!("Hello {}!", cli.name);
}
}
#[derive(Parser)]
: Implements the necessary traits to parse arguments into this struct.#[command(...)]
: Configures application-level details like version, author, and the "about" description (often taken from doc comments).- Struct fields define the arguments themselves.
Cli::parse()
: Parsesstd::env::args()
based on the struct definition. It will automatically handle errors (like missing arguments) and commands like--help
or--version
, potentially exiting the program.
4.2 Defining Arguments with #[arg]
The #[arg(...)]
attribute on struct fields configures individual arguments:
short
: Defines a single-character short flag (e.g.,#[arg(short = 'c')]
or just#[arg(short)]
to infer from the field name).long
: Defines a long flag (e.g.,#[arg(long = "config")]
or just#[arg(long)]
to infer from the field name).default_value = "..."
: Provides a default string value if the argument isn't provided.default_value_t = ...
: Provides a default typed value (requires the type to implementClone
).action = ...
: Specifies the behavior, e.g.,clap::ArgAction::SetTrue
for boolean flags,clap::ArgAction::Count
for counting occurrences (-vvv
).value_parser = ...
: Allows specifying custom parsing logic.- Doc comments on fields are automatically used as help descriptions for the argument.
4.3 Argument Types (Flags, Options, Positionals)
clap
generally infers the type of argument from the field type and attributes:
- Flags: Typically fields of type
bool
withaction = clap::ArgAction::SetTrue
. They don't take a value (e.g.,--verbose
). - Options: Fields with
short
orlong
attributes that expect a value (e.g.,String
,i32
,PathBuf
). Example:--output file.txt
. UseOption
if the option is optional, otherwise it's usually required. UseVec
to accept multiple values (--input a --input b
). - Positionals: Fields without
short
orlong
attributes. Their order matters. Example:cp SOURCE DEST
whereSOURCE
andDEST
are positional arguments.
5. Handling Subcommands
Many CLIs use subcommands (like git commit
, cargo build
). clap
handles this using an enum derived from Subcommand
.
5.1 Defining the Subcommand
Enum
First, add a field to your main Parser
struct to hold the subcommand, and derive Subcommand
for an enum defining the possible subcommands.
use clap::{Parser, Subcommand};
use std::path::PathBuf;
#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
struct Cli {
/// Turn debugging information on
#[arg(short, long, action = clap::ArgAction::SetTrue)]
debug: bool,
#[command(subcommand)]
command: Commands, // Field to hold the chosen subcommand
}
#[derive(Subcommand, Debug)]
enum Commands {
/// Adds files to myapp
Add {
/// Files to add
#[arg(required = true)]
files: Vec,
},
/// Removes files from myapp
Remove {
/// File to remove
file: PathBuf,
},
}
fn main() {
let cli = Cli::parse();
// You can check the value of the debug flag (`cli.debug`) before handling the subcommand
if cli.debug {
println!("Debug mode is on.");
}
// Handle the specific subcommand
match &cli.command {
Commands::Add { files } => {
println!("Adding files: {:?}", files);
// Add logic here...
}
Commands::Remove { file } => {
println!("Removing file: {:?}", file);
// Remove logic here...
}
}
}
- The
command
field inCli
has the#[command(subcommand)]
attribute. - The
Commands
enum derivesSubcommand
. - Each variant of the enum represents a subcommand.
- Struct-like variants (
Add { ... }
) allow the subcommand itself to have arguments, defined just like fields in aParser
struct.
5.2 Parsing and Matching Subcommands
After calling Cli::parse()
, you use a match
statement on the subcommand field (cli.command
in the example) to execute the logic specific to the chosen subcommand and access its arguments.
6. Automatic Help and Version
By using the version
and about
attributes on the main Parser
struct (or letting them default to Cargo.toml/doc comments), `clap` automatically handles the --help
/-h
and --version
/-V
flags, generating well-formatted output and exiting.
$ ./my_cli_app --help
# Output will show usage, description, arguments, options, subcommands...
$ ./my_cli_app --version
# Output will show the version from Cargo.toml
7. Builder API (Brief Mention)
clap
also offers a Builder API, where you construct the command structure programmatically using function calls (e.g., Command::new().arg(...).subcommand(...)
). This provides more runtime flexibility but is generally more verbose than the Derive API. The Derive API is recommended for most use cases.
8. Conclusion
clap
is the go-to crate for building robust, user-friendly command-line applications in Rust. Its Derive API provides an ergonomic way to define arguments, options, flags, and subcommands using simple struct and enum definitions with attributes. Features like automatic help/version generation and argument validation significantly reduce boilerplate and improve the quality of your CLIs.
By leveraging clap
, you can focus on your application's core logic while providing a professional and intuitive interface for your users.