clap (Rust): Derive API, Subcommands & Env Vars Guide

Max WellsMax WellsFounder of Rustify

TL;DR: clap (Command Line Argument Parser) is the standard way to parse argv in Rust. The derive API lets you define your CLI as a struct with #[derive(Parser)]; clap generates the parser, help text, and validation automatically. You get subcommands, typed arguments, default values, environment variable fallbacks, and shell completion generation out of the box.


How Do You Use clap With the Derive API?

Annotate a struct with #[derive(Parser)]; clap reads the struct fields and doc comments to build the full CLI automatically.

[dependencies]
clap = { version = "4", features = ["derive"] }
use clap::Parser;
 
/// A fast file search tool
#[derive(Parser, Debug)]
#[command(version, about, long_about = None)]
struct Args {
    /// Pattern to search for
    pattern: String,
 
    /// File to search in
    path: std::path::PathBuf,
 
    /// Show line numbers
    #[arg(short, long)]
    line_numbers: bool,
 
    /// Max results to show (default: 100)
    #[arg(short, long, default_value_t = 100)]
    max_results: usize,
}
 
fn main() {
    let args = Args::parse();
    println!("Searching for '{}' in {:?}", args.pattern, args.path);
}

Running cargo run -- --help outputs:

A fast file search tool
 
Usage: myapp [OPTIONS] <PATTERN> <PATH>
 
Arguments:
  <PATTERN>  Pattern to search for
  <PATH>     File to search in
 
Options:
  -l, --line-numbers        Show line numbers
  -m, --max-results <N>     Max results to show [default: 100]
  -h, --help                Print help
  -V, --version             Print version

How Do You Define Subcommands?

Use an enum with #[derive(Subcommand)]; each variant is a subcommand with its own arguments.

use clap::{Parser, Subcommand};
 
#[derive(Parser)]
#[command(version, about)]
struct Cli {
    #[command(subcommand)]
    command: Commands,
}
 
#[derive(Subcommand)]
enum Commands {
    /// Add a new item
    Add {
        /// Name of the item
        name: String,
    },
    /// List all items
    List {
        /// Show only active items
        #[arg(short, long)]
        active: bool,
    },
    /// Remove an item by ID
    Remove { id: u64 },
}
 
fn main() {
    let cli = Cli::parse();
    match cli.command {
        Commands::Add { name } => println!("Adding: {name}"),
        Commands::List { active } => println!("Listing (active={active})"),
        Commands::Remove { id } => println!("Removing: {id}"),
    }
}

How Do You Read From Environment Variables?

Use #[arg(env = "VAR_NAME")] to fall back to an environment variable if the flag isn't provided.

use clap::Parser;
 
#[derive(Parser)]
struct Config {
    /// Database URL (or DATABASE_URL env var)
    #[arg(long, env = "DATABASE_URL")]
    database_url: String,
 
    /// API key (or API_KEY env var)
    #[arg(long, env = "API_KEY", hide_env_values = true)]
    api_key: String,
}

Priority: CLI flag > environment variable > default value. hide_env_values = true prevents the env value from showing in help output (important for secrets).


What Is the Builder API?

The builder API constructs the CLI programmatically with method chains; useful when the structure is dynamic or when you can't use derive.

use clap::{Arg, Command};
 
fn main() {
    let matches = Command::new("myapp")
        .version("1.0")
        .arg(Arg::new("pattern").required(true))
        .arg(
            Arg::new("verbose")
                .short('v')
                .long("verbose")
                .action(clap::ArgAction::SetTrue),
        )
        .get_matches();
 
    let pattern = matches.get_one::<String>("pattern").unwrap();
    let verbose = matches.get_flag("verbose");
}

For most use cases, the derive API is clearer and more maintainable.


Frequently Asked Questions

structopt was the original derive-based wrapper around clap. As of clap v3+, the derive API is built directly into clap; structopt is now deprecated. Use clap with features = ["derive"].

Use #[arg(value_parser = ...)] with a custom function, or use clap's built-in value parsers for ranges and enums:

#[arg(value_parser = clap::value_parser!(u16).range(1..=65535))]
port: u16,

Yes; use clap_complete crate to generate completion scripts for Bash, Zsh, Fish, PowerShell, and Elvish.

Positional arguments are required by default. Wrap the type in Option<T> to make it optional. Flags (--flag) are optional by default; add .required(true) to make them mandatory.


Sources


  • Struct: clap's derive API annotates structs to define CLI arguments
  • Enum: Subcommands are typically defined as Rust enums
  • Serde: Often paired with clap to load config from files

Keep Reading

Ready to Land a $120k+ Rust Job in the US or Europe?