clap
Derive-based argument parsing with validation, help generation, and subcommands.
Recipe
use clap::Parser;
#[derive(Parser)]
#[command(name = "greet", version, about = "Greet someone")]
struct Args {
/// Name to greet
name: String,
/// Number of times to greet
#[arg(short, long, default_value_t = 1)]
count: u8,
}
fn main() {
let args = Args::parse();
for _ in 0..args.count {
println!("Hello, {}!", args.name);
}
}When to reach for this: Any CLI with more than one or two flags. clap is the de facto standard in the Rust ecosystem.
Working Example
use clap::{Parser, Subcommand, ValueEnum};
use std::path::PathBuf;
#[derive(Parser)]
#[command(name = "backup", version, about = "Backup files")]
struct Cli {
/// Increase verbosity (-v, -vv)
#[arg(short, long, action = clap::ArgAction::Count)]
verbose: u8,
/// Config file path
#[arg(short, long, env = "BACKUP_CONFIG")]
config: Option<PathBuf>,
#[command(subcommand)]
command: Command,
}
#[derive(Subcommand)]
enum Command {
/// Create a new backup
Create {
#[arg(value_enum)]
format: Format,
source: PathBuf,
},
/// List existing backups
List,
}
#[derive(Clone, ValueEnum)]
enum Format {
Tar,
Zip,
}
fn main() -> anyhow::Result<()> {
let cli = Cli::parse();
match cli.command {
Command::Create { format, source } => {
println!("backing up {source:?} as {format:?}");
}
Command::List => println!("no backups yet"),
}
Ok(())
}What this demonstrates:
- Derive macros generate
--helpand validation automatically env =binds environment variables to flagsValueEnumrestricts choices to a typed set- Subcommands organize multi-verb CLIs
Deep Dive
How It Works
#[derive(Parser)]expands to aparse()implementation at compile time- Doc comments on fields become help text
ArgAction::Countimplements-v/-vvverbosity patterns- Errors print usage hints and exit with code 2
Common Attributes
| Attribute | Purpose |
|---|---|
short, long | -n and --name flags |
default_value_t | Default when flag omitted |
required = true | Fail if argument missing |
conflicts_with | Mutually exclusive flags |
requires | Dependent flag groups |
Builder API Alternative
Use the builder API when you need dynamic arguments at runtime.
use clap::{Arg, Command};
let cmd = Command::new("dynamic")
.arg(Arg::new("file").required(true));
let matches = cmd.get_matches();Gotchas
- Missing
derivefeature - Addclap = { version = "4", features = ["derive"] }. Fix: Enable the derive feature inCargo.toml. - Enum subcommands need
Subcommandderive - Plain enums fail to compile. Fix: Add#[derive(Subcommand)]. default_valuevsdefault_value_t- String defaults need quotes; typed defaults use_t. Fix: Match the attribute to your field type.- Flattening nested structs -
#[command(flatten)]merges args but can cause name collisions. Fix: Use unique field names or explicitid. - Help version drift - Hardcoded version strings go stale. Fix: Use
#[command(version = env!("CARGO_PKG_VERSION"))].
Alternatives
| Alternative | Use When | Don't Use When |
|---|---|---|
argh | Minimal binary size, Google-style flags | You need rich subcommands |
pico-args | Tiny dependency footprint | Complex validation rules |
Manual std::env::args | Learning or throwaway scripts | Production tools with --help |
FAQs
clap 3 vs clap 4?
clap 4 is the current stable API. New projects should use clap 4 with derive macros.
How do I require a subcommand?
Add #[command(subcommand_required = true)] on the root Parser struct.
Can I parse from a Vec instead of argv?
Yes. Use Args::parse_from(["prog", "--flag", "value"]) in tests.
How do I hide internal flags?
Use #[arg(hide = true)] on the field.
Global flags across subcommands?
Place flags on the root struct. They apply to all subcommands automatically.
How do I validate a path exists?
Use value_parser = clap::value_parser!(PathBuf) plus a custom validator, or check in main after parsing.
Shell completions?
Use clap_complete crate. See the shell completions page.
Testing clap parsers?
Call parse_from with synthetic argv in unit tests. Assert on Err for invalid input.
How do I show defaults in help?
Defaults appear automatically when you set default_value or default_value_t.
Multiline about text?
Use #[command(about = "...")] or long_about for extended descriptions.
Related
- CLI Basics - fundamentals
- Configuration & Env - env var binding
- Shell Completions & Man Pages - completions
- Error Reporting - user-friendly errors
Stack versions: This page was written for Rust 1.97.0 (edition 2024), Tokio 1.x, Axum 0.8, serde 1.0, sqlx 0.8, clap 4, and Polars 0.46+.