Skip to content

Instantly share code, notes, and snippets.

@andreimerlescu
Created May 18, 2026 01:31
Show Gist options
  • Select an option

  • Save andreimerlescu/1aa0d1ce423cc17c6773d59680a71ce3 to your computer and use it in GitHub Desktop.

Select an option

Save andreimerlescu/1aa0d1ce423cc17c6773d59680a71ce3 to your computer and use it in GitHub Desktop.

The basics

#[arg()] decorates a field in a #[derive(Parser)] struct and tells clap how to parse that field from the command line. Every option inside #[arg()] is a key-value pair.


Naming

long — creates a --flag style argument. If you write #[arg(long)] with no value, it uses the field name. If you write #[arg(long = "1p")] it uses that string instead. That's why we have:

#[arg(long = "1p", ...)]
use_op: bool,

Because the field is named use_op but the CLI flag is --1p.

short — creates a -f style single-character flag. #[arg(short)] uses the first letter of the field name. #[arg(short = 'f')] uses that character explicitly. You can combine both:

#[arg(short, long)]
find: Option<String>,  // accepts both -f and --find

alias — adds an alternative long name:

#[arg(long, alias = "substring")]
find: Option<String>,  // accepts --find or --substring

short_alias — same but for short flags.


Values and types

default_value — the string value used when the argument isn't provided:

#[arg(long, default_value = "ed25519")]
algorithm: String,

default_value_t — like default_value but takes a typed Rust expression instead of a string, avoiding the parse step:

#[arg(long, default_value_t = 0)]
cores: usize,

default_value_if — sets a default conditionally based on another argument's value:

#[arg(long, default_value_if("algorithm", "secp256k1", "some-default"))]
cores: usize,

value_name — the placeholder shown in help text. Without it clap uses the uppercased field name:

#[arg(long, value_name = "SUBSTRING")]
find: Option<String>,
// shows: --find <SUBSTRING>

value_parser — custom parsing logic. Can be a closure or a type implementing clap::builder::TypedValueParser:

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

value_enum — tells clap the type is an enum deriving clap::ValueEnum, enabling tab completion and validation automatically.

num_args — how many values the argument accepts:

#[arg(long, num_args = 1..=3)]
files: Vec<String>,  // accepts 1, 2, or 3 values

value_delimiter — splits a single string value on a delimiter:

#[arg(long, value_delimiter = ',')]
tags: Vec<String>,  // --tags a,b,c becomes ["a", "b", "c"]

Behavior

required — makes the argument mandatory, overriding Option<T> inference:

#[arg(long, required = true)]
find: Option<String>,

required_unless_present — required unless another named argument is present:

#[arg(long, required_unless_present = "begins")]
find: Option<String>,

required_unless_present_any — required unless any of a list are present.

required_unless_present_all — required unless all of a list are present.

conflicts_with — errors if both this and another argument are provided:

#[arg(long, conflicts_with = "ends")]
begins: Option<String>,

conflicts_with_all — same but a list.

requires — this argument requires another to also be present.

requires_all — requires all of a list.

action — controls what happens when the flag is encountered. The most common values are:

// SetTrue: sets bool to true when flag present (what --1p uses)
#[arg(long, action = clap::ArgAction::SetTrue)]
use_op: bool,

// SetFalse: sets bool to false when flag present
#[arg(long, action = clap::ArgAction::SetFalse)]
no_color: bool,

// Count: counts how many times the flag appears (-vvv = 3)
#[arg(short, action = clap::ArgAction::Count)]
verbose: u8,

// Append: collects multiple values into a Vec
#[arg(long, action = clap::ArgAction::Append)]
tag: Vec<String>,

global — makes the argument available to subcommands automatically.


Environment variables

env — reads from an environment variable as a fallback when the flag isn't passed. CLI always wins over env:

#[arg(long, env = "FIND")]
find: Option<String>,
// reads $FIND if --find not provided

hide_env — stops the env var name from appearing in help text.

hide_env_values — shows the env var name in help but hides its current value (useful for secrets).


Help and documentation

help — the short description shown next to the argument in --help:

#[arg(long, help = "substring to search for in rAddress")]
find: Option<String>,

long_help — shown when --help is used (some CLIs differentiate -h short vs --help long):

#[arg(long, long_help = "The substring to search for in the rAddress. \
    Case insensitive by default. Must be alphanumeric.")]
find: Option<String>,

hide — hides the argument from help output entirely but it still works:

#[arg(long, hide = true)]
debug_mode: bool,

hide_short_help — hides from -h but shows in --help.

hide_long_help — hides from --help but shows in -h.

display_order — controls the order arguments appear in help output. Lower numbers appear first:

#[arg(long, display_order = 1)]
find: Option<String>,

Validation

allow_hyphen_values — permits values that start with -, which clap would normally interpret as a flag:

#[arg(long, allow_hyphen_values = true)]
pattern: String,  // allows --pattern -abc

allow_negative_numbers — permits negative numeric values.

ignore_case — case-insensitive matching for value_enum types.


Positional arguments

If you omit long and short, the argument becomes positional — it's matched by position in the command line rather than by flag name:

#[arg()]
input_file: String,
// invoked as: find-xrp-addr myfile.txt  (no --input-file needed)

last — marks the argument as the last positional, only valid after --:

#[arg(last = true)]
extra_args: Vec<String>,

The full picture in our program

Looking at our actual fields with fresh eyes:

#[arg(long, env = "FIND")]
find: Option<String>,
// long:  --find
// env:   $FIND
// type:  Option means not required

#[arg(long, default_value_t = true, env = "INSENSITIVE")]
insensitive: bool,
// long:          --insensitive
// default_value_t: true means it's on unless --insensitive=false
// env:           $INSENSITIVE

#[arg(long, default_value_t = 0, env = "CORES")]
cores: usize,
// 0 is our sentinel meaning "use all available"

#[arg(long = "1p", env = "USE_ONEPASSWORD", action = clap::ArgAction::SetTrue)]
use_op: bool,
// long = "1p":  overrides field name because --use-op isn't what we want
// SetTrue:      presence of --1p sets this to true, absence leaves it false
// without SetTrue on a bool, clap would require --1p=true explicitly

The action = clap::ArgAction::SetTrue on use_op is subtle but important — without it, a bool field requires the user to write --1p=true explicitly. With SetTrue, just writing --1p is enough.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment