clap / Chapter 1 of 5

Arguments, options, and flags

Parse Rust command-line arguments with clap derive. Examples cover flags, defaults, optional values, typed validation, lists, and help text.

7 topicsUpdated
01 / 27

Start here: your first CLI#

A command line interface reads words after the program name and turns them into typed Rust values.

Add clap to a binary crate. The derive feature enables #[derive(Parser)]. The extra env feature is used later for environment variables.

ShellCreate a project
cargo new shelf
cd shelf
cargo add clap --features derive,env
Rustsrc/main.rs — a complete first program
use clap::Parser;

/// Print a greeting.
#[derive(Debug, Parser)]
#[command(version)]
struct Cli {
    /// Person to greet.
    name: String,

    /// Repeat the greeting this many times.
    #[arg(short, long, default_value_t = 1)]
    count: u8,

    /// Use a shorter greeting.
    #[arg(short, long)]
    brief: bool,
}

fn main() {
    let cli = Cli::parse();
    for _ in 0..cli.count {
        if cli.brief {
            println!("Hi, {}!", cli.name);
        } else {
            println!("Hello, {}!", cli.name);
        }
    }
}
ShellTry it
cargo run -- Ada -c 2
# Hello, Ada!
# Hello, Ada!

cargo run -- Ada --brief
# Hi, Ada!

cargo run -- --help
The separator

cargo run -- tells Cargo to pass the remaining words to your program. Run the compiled binary directly without that separator.

02 / 27

Arguments, options, and flags#

Each field in the parser describes one piece of input. The field type determines what Rust value you receive.

CLI formExampleRust fieldMeaning
Positionalshelf book.txtinput: PathBufA value in a particular position.
Optionshelf --output out.txt#[arg(long)] output: PathBufA named setting that takes a value.
Flagshelf --dry-run#[arg(long)] dry_run: boolA named on/off switch; false when absent.
Short optionshelf -o out.txt#[arg(short)] output: PathBufA one-letter spelling.

Field names such as dry_run become --dry-run. #[arg(short, long)] exposes both -o and --output. A plain String positional is required unless you give it a default or wrap it in Option.

RustMix positionals and named options
use std::path::PathBuf;
use clap::Parser;

#[derive(Parser)]
struct Cli {
    input: PathBuf,
    #[arg(short, long)]
    output: Option<PathBuf>,
    #[arg(long)]
    dry_run: bool,
}

fn main() {
    let args = Cli::parse();
    println!("input={:?} output={:?} dry_run={}", args.input, args.output, args.dry_run);
}
ShellCall it
cargo run -- notes.txt -o result.txt --dry-run
# input="notes.txt" output=Some("result.txt") dry_run=true
03 / 27

Help, version, and discoverability#

Make every command understandable without reading its source.

A /// comment on a parser or field becomes help text. #[command(version)] reads the crate version from Cargo metadata and enables -V/--version. Clap supplies -h/--help. Write usage-oriented descriptions: what a flag changes and what a value means.

RustDocument a command
use std::path::PathBuf;
use clap::Parser;

/// Search text files for a pattern.
#[derive(Parser)]
#[command(version, about, long_about = None)]
struct Cli {
    /// Text to look for.
    pattern: String,
    /// File to search.
    file: PathBuf,
    /// Match without caring about letter case.
    #[arg(short, long)]
    ignore_case: bool,
}

fn main() {
    let _ = Cli::parse();
}
ShellDiscover usage
cargo run -- --help
cargo run -- --version
cargo run -- needle ./notes.txt --ignore-case
Short and long help

When you provide longer doc comments with paragraphs, -h and --help can show different amounts of detail. Use long_about = None when you want the same short summary in both.

04 / 27

Required values, defaults, and optional input#

Use Rust types to express whether a user must supply a value.

RustCommon field shapes
use clap::Parser;

#[derive(Parser, Debug)]
struct Cli {
    /// Required positional.
    input: String,

    /// Optional named value.
    #[arg(long)]
    output: Option<String>,

    /// Optional named value with a typed default.
    #[arg(long, default_value_t = 10)]
    limit: usize,

    /// Named switch, false when absent.
    #[arg(long)]
    quiet: bool,
}

fn main() {
    println!("{:#?}", Cli::parse());
}

A required --token can be written as #[arg(long)] token: String; omitting it is a parse error. Use Option<T> to permit absence. default_value_t needs a value that can be displayed and parsed back into its type.

ShellCLI behavior
cargo run -- input.txt --limit 25
cargo run --                 # error: required <INPUT> is missing
cargo run -- input.txt --quiet
Optional flag values

The uncommon Option<Option<T>> accepts a flag with an optional value; decide what a bare flag means with default_missing_value when appropriate. A simple Option<T> still needs a value after the flag.

05 / 27

Typed values and validation#

Let clap reject malformed input before your command does any work.

Numeric fields, paths, IP addresses, and other supported types are parsed for you. For a finite set of values, derive ValueEnum. For numeric bounds, use value_parser!. A custom type can implement FromStr and return a useful error message.

RustAllowed choices and a numeric range
use clap::{Parser, ValueEnum};

#[derive(Clone, Debug, ValueEnum)]
enum Format {
    Text,
    Json,
}

#[derive(Parser, Debug)]
struct Cli {
    #[arg(long, value_enum, default_value_t = Format::Text)]
    format: Format,
    #[arg(long, value_parser = clap::value_parser!(u16).range(1..=65535))]
    port: u16,
}

impl std::fmt::Display for Format {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", match self { Self::Text => "text", Self::Json => "json" })
    }
}

fn main() {
    println!("{:?}", Cli::parse());
}
ShellValid and invalid values
cargo run -- --port 8080 --format json
cargo run -- --port 0      # clap rejects an out-of-range port
cargo run -- --port 8080 --format xml  # clap lists the allowed values
Defaults and enums

A typed default for ValueEnum must display to a value the enum parser accepts. A simpler alternative is #[arg(value_enum, default_value = "text")] with no Display implementation.

06 / 27

Custom validation for domain values#

Reject an invalid value with a message that tells the user what to fix.

For rules beyond a built-in number range, supply a parsing function. It receives the supplied string and returns either the typed value or an error message. Clap prints that message with its own usage hint.

RustAccept a percentage from 0 to 100
use clap::Parser;

fn parse_percentage(raw: &str) -> Result<u8, String> {
    let value: u8 = raw.parse().map_err(|_| "expected a whole number".to_owned())?;
    if value <= 100 { Ok(value) }
    else { Err("percentage must be at most 100".to_owned()) }
}

#[derive(Parser)]
struct Cli {
    #[arg(long, value_parser = parse_percentage)]
    progress: u8,
}

fn main() {
    println!("{}%", Cli::parse().progress);
}
ShellThe error happens during parsing
cargo run -- --progress 75    # 75%
cargo run -- --progress 120   # percentage must be at most 100
cargo run -- --progress many  # expected a whole number
Where validation belongs

Validate the shape and meaning of individual arguments in the parser. Rules that require reading a file or calling a service belong in the work phase and should return a normal application error.

07 / 27

Lists, repeated options, and counters#

Collect several values without writing your own argument loop.

RustRepeat a flag, append values, split a list
use clap::{ArgAction, Parser};

#[derive(Debug, Parser)]
struct Cli {
    /// Each -v increases verbosity.
    #[arg(short, long, action = ArgAction::Count)]
    verbose: u8,

    /// Supply more than once: --tag red --tag blue.
    #[arg(long)]
    tag: Vec<String>,

    /// One flag with comma-separated fields.
    #[arg(long, value_delimiter = ',')]
    columns: Vec<String>,

    /// One or more input files.
    #[arg(required = true, num_args = 1..)]
    files: Vec<String>,
}

fn main() {
    println!("{:#?}", Cli::parse());
}
ShellTry the different forms
cargo run -- -vv --tag red --tag blue --columns name,size a.txt b.txt
# verbose: 2
# tag: ["red", "blue"]
# columns: ["name", "size"]
# files: ["a.txt", "b.txt"]
What a Vec means

A Vec<T> on a named option normally collects repeated occurrences. num_args changes values per occurrence; value_delimiter splits one supplied value. They solve different problems.