Skip to content
Select theme

How to get a good --help for free

Short answer: clap builds --help from three things already in the code: the /// doc comments, the default_value_t values, and whether a field is String or Option<String>. Keep those three honest and the help text looks after itself.

The examples stay in the args1 project from the previous article, so the usage lines below say args1; a project with another name prints that name instead.

Cargo.toml
[dependencies]
clap = { version = "4.6.7", features = ["derive"] }

Replace src/main.rs with the greeter from the previous article, with three edits that change what --help prints:

src/main.rs
use clap::{Parser, Subcommand};
/// A small tool with two commands
#[derive(Parser)]
struct Cli {
#[command(subcommand)]
command: Command,
/// Print the parsed command as well
#[arg(short, long, global = true)]
verbose: bool,
}
#[derive(Debug, Subcommand)]
enum Command {
/// Greet someone
Greet {
/// Who to greet
name: String,
/// How many times to greet them
#[arg(short, long, default_value_t = 1)]
count: u8,
},
/// Say goodbye to someone, or to everyone
Bye {
/// Who to say goodbye to; leave it out to say goodbye to everyone
name: Option<String>,
},
}
fn main() {
let cli = Cli::parse();
if cli.verbose {
eprintln!("verbose: {:?}", cli.command);
}
match cli.command {
Command::Greet { name, count } => {
for _ in 0..count {
println!("hello, {name}");
}
}
Command::Bye { name } => {
let name = name.unwrap_or_else(|| "everyone".to_string());
println!("bye, {name}");
}
}
}
$ cargo run -q -- --help
A small tool with two commands
Usage: args1 [OPTIONS] <COMMAND>
Commands:
greet Greet someone
bye Say goodbye to someone, or to everyone
help Print this message or the help of the given subcommand(s)
Options:
-v, --verbose Print the parsed command as well
-h, --help Print help

Three doc comments write that page. The one above struct Cli is the first line; it does the same job as the about = "…" attribute the previous article used, and it sits directly above the struct it describes. The comment above each field is the text in that field’s row, and each variant’s comment is its line under Commands. One level down, the same comments appear again:

$ cargo run -q -- greet --help
Greet someone
Usage: args1 greet [OPTIONS] <NAME>
Arguments:
<NAME> Who to greet
Options:
-c, --count <COUNT> How many times to greet them [default: 1]
-v, --verbose Print the parsed command as well
-h, --help Print help

Nothing on that page was typed twice: Greet someone, Who to greet, and the --count row are the /// lines from the file above. --verbose shows up here too because it is global, as in the previous article.

The [default: 1] comes from default_value_t = 1, and it’s the only copy of that number. Change the attribute to 3 and the row follows:

-c, --count <COUNT> How many times to greet them [default: 3]

The default is also what makes the option optional. Remove the attribute and clap makes --count required instead: the usage line grows a --count <COUNT>, and omitting the flag is an error with exit code 2.

$ cargo run -q -- greet Ada
error: the following required arguments were not provided:
--count <COUNT>
Usage: args1 greet --count <COUNT> <NAME>
For more information, try '--help'.

For a positional argument the switch is the type, not an attribute. greet’s name is a String, so the usage line reads <NAME> and clap refuses to run without it:

$ cargo run -q -- greet
error: the following required arguments were not provided:
<NAME>
Usage: args1 greet <NAME>
For more information, try '--help'.

bye’s name is an Option<String>, so the usage line reads [NAME] and the argument may be left out. The cost is the branch in main that says what absent means:

$ cargo run -q -- bye --help
Say goodbye to someone, or to everyone
Usage: args1 bye [OPTIONS] [NAME]
Arguments:
[NAME] Who to say goodbye to; leave it out to say goodbye to everyone
Options:
-v, --verbose Print the parsed command as well
-h, --help Print help
$ cargo run -q -- bye
bye, everyone
$ cargo run -q -- bye Bob
bye, Bob