Parsing CLI Arguments in Rust with clap's Derive API
Turn a plain struct into a full argument parser, complete with flags, options, help text and --version, in about a dozen lines.
Every command-line tool starts the same way: it needs to read some arguments. You can loop over std::env::args() by hand, but you'll be rewriting help text, validation and error messages within the hour. The clap crate handles all of that, and its derive API lets you describe your whole CLI as a plain struct.
Set it up
cargo new linecount
cd linecount
cargo add clap --features derive
Describe the arguments as a struct
use std::path::PathBuf;
use clap::Parser;
/// A tiny example CLI
#[derive(Parser, Debug)]
#[command(version)]
struct Cli {
/// File to read
path: PathBuf,
/// Print extra detail
#[arg(short, long)]
verbose: bool,
/// Stop after this many lines
#[arg(short = 'n', long, default_value_t = 1000)]
limit: usize,
}
fn main() {
let cli = Cli::parse();
if cli.verbose {
eprintln!("verbose mode on");
}
println!("would read up to {} lines from {}", cli.limit, cli.path.display());
}
That's the whole parser. A few things are happening here:
- A field with no attribute (
path) becomes a required positional argument. - A
boolfield becomes a flag.shortandlonggive you-vand--verbose. - The field's type is the validation. Pass
--limit abcand clap rejects it with a clear error, becauseabcisn't ausize. - Doc comments (
///) become the help text, so your documentation and your--helpnever drift apart.
Try it
cargo run -- --help now prints something like this, with no extra code:
A tiny example CLI
Usage: linecount [OPTIONS]
Arguments:
File to read
Options:
-v, --verbose Print extra detail
-n, --limit Stop after this many lines [default: 1000]
-h, --help Print help
-V, --version Print version
Cli::parse() also handles the failure path for you: on bad input it prints the error and exits with status 2, the conventional code for a usage error. If you'd rather handle that yourself, Cli::try_parse() returns a Result instead.
From here, the natural next steps are subcommands (put #[derive(Subcommand)] on an enum) and value validation with value_parser. Both are just more attributes on the same struct.