🎓 A1Learner
🔍

Rust Clap Arguments

Clap is the standard crate for parsing command line arguments. Clap reads the arguments into a struct that describes them.

Matching words by hand

Without a parser crate, each argument is found, parsed, and checked by hand:

fn main() {
    let args: Vec<String> = std::env::args().collect();
    let name = &args[1];
    let count: u8 = args[2].parse().unwrap();
    let loud = args.len() > 3 && args[3] == "--loud";
    for _ in 0..count {
        println!("Hello, {}!", name);
    }
    println!("loud = {}", loud);
}

A required word

One field, no attributes: the program demands exactly one word and calls it name.

use clap::Parser;

#[derive(Parser)]
struct Cli {
    /// Who to greet
    name: String,
}

let cli = Cli::parse();

An optional number and a flag

default_value_t fills count when it is absent. A bare bool is a flag — --loud or nothing.

#[derive(Parser)]
struct Cli {
    /// Who to greet
    name: String,
    /// How many greetings
    #[arg(short, long, default_value_t = 1)]
    count: u8,
    /// Shout the greeting
    #[arg(short, long)]
    loud: bool,
}

Only these answers (ValueEnum)

Derive ValueEnum on your own enum. The field then accepts only its lowercase variants. --style rude is rejected with a helpful error.

#[derive(Copy, Clone, PartialEq, Eq, clap::ValueEnum)]
enum Style {
    Friendly,
    Formal,
}

/// Greeting style
#[arg(long, value_enum)]
style: Option<Style>,

Subcommands

One enum, one variant per command. Each variant's fields become that command's own arguments. List them with #[command(subcommand)].

#[derive(clap::Subcommand)]
enum Command {
    /// Add a task to the list
    Add { text: String },
    /// Show the tasks
    List,
}

#[command(subcommand)]
command: Option<Command>,

Check a number's range for free

value_parser with a range rejects out-of-range values before your code ever runs — no manual bounds checking.

/// Cleanup threshold, in percent
#[arg(long, value_parser = clap::value_parser!(u8).range(0..=100))]
percent: Option<u8>,

Arguments that refuse each other

conflicts_with names a flag that cannot appear beside this one. Run with --json --yaml and clap rejects the pair. The first error line is:

/// Print the plan as JSON
#[arg(long, conflicts_with = "yaml")]
json: bool,
/// Print the plan as YAML
#[arg(long)]
yaml: bool,

One flag demands another

requires names a flag that must come along. Strict mode means little without a level. Run with --strict alone and clap asks for the missing flag:

/// Check strictly (needs --level)
#[arg(long, requires = "level")]
strict: bool,
/// How deep to check
#[arg(long)]
level: Option<u8>,

Example

use clap::Parser;

/// Greets a person, politely or loudly
#[derive(Parser)]
struct Cli {
    /// Who to greet
    name: String,
    /// How many greetings
    #[arg(short, long, default_value_t = 1)]
    count: u8,
    /// Shout the greeting
    #[arg(short, long)]
    loud: bool,
}

fn main() {
    let cli = Cli::parse();
    let greeting = format!("Hello, {}!", cli.name);
    let greeting = if cli.loud { greeting.to_uppercase() } else { greeting };
    for _ in 0..cli.count {
        println!("{greeting}");
    }
}

Key points

  • Clap reads the command line into a struct and checks each value on the way.
  • Add clap = { version = "4", features = ["derive"] } under [dependencies] — the derive feature switches on the attribute style used here.
  • A plain field is required and positional.
  • Option<T> makes it optional (None when absent).
  • A bool makes a flag that is true only when typed.
  • Doc comments on the fields become the --help text — write them for the user, not for yourself.
  • Use conflicts_with to ban a pair, and requires to demand a partner.
  • Your main code then never sees the bad combination.

Run with: cargo run -- Ferris -c 2 --loud. Ferris fills the positional name. -c 2 sets count (short for --count). --loud flips the flag. So the greeting is built once, uppercased once, then printed twice. Run it with no arguments and clap prints an error plus usage. It exits with code 2. The struct never even gets built. --help shows the doc comments as the field descriptions.

Note

When parsing fails, clap prints the error and a usage line.

A missing required argument or a bad number can cause it.

Then it exits with code 2.

Your code never sees the mistake.

Try it Yourself

Edit the code, then press Run.

Running is simulated · Rust Playground

Exercise

In clap's derive style, which field type makes an argument optional?