Skip to content

Project: Command-Line Tool with clap

On the Command-Line Arguments page you read arguments by hand with std::env::args(). That works for one or two arguments. But real tools need options like --precision 3, short forms like -p 3, a --help page, good error messages, and subcommands like git commit and git push. Writing all of that by hand is a lot of boring, error-prone code.

clap (Command Line Argument Parser) is the crate almost every Rust CLI uses for this. You describe your arguments as a normal Rust struct, and clap writes the parser, the help text and the error messages for you. In this project you’ll build convert, a small unit converter with two subcommands: temp for temperatures and length for lengths.

Here’s the finished tool in a terminal. Every output on this page comes from actually running the program.

Terminal window
$ cargo run -q -- temp 100 --from celsius --to fahrenheit
100 °C = 212.00 °F
$ cargo run -q -- temp -40 -f c -t f
-40 °C = -40.00 °F
$ cargo run -q -- length 5 km mi
5 km = 3.11 mi
$ cargo run -q -- -p 0 length 1 mi ft
1 mi = 5280 ft
$ cargo run -q -- temp 20 --from rankine --to c
error: invalid value 'rankine' for '--from <FROM>'
[possible values: celsius, fahrenheit, kelvin]
For more information, try '--help'.

Everything after -- goes to your program instead of to Cargo, and -q (quiet) hides Cargo’s own “Compiling…” lines. That last error message? We didn’t write it. clap did.

Terminal window
cargo new convert
cd convert
cargo add clap --features derive

The derive feature is the important part. It turns on #[derive(Parser)], which is the friendly way to use clap. Your Cargo.toml now contains:

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

(Your version number may be a little newer. Anything starting with 4 works for this page. See Cargo and Crates if cargo add is new to you.)

Let’s start with a tool that only converts Celsius to Fahrenheit. You write a struct with one field per argument and put #[derive(Parser)] on it. Replace src/main.rs with:

use clap::Parser;
/// Convert a temperature from Celsius to Fahrenheit
#[derive(Parser, Debug)]
#[command(name = "convert", version = "1.0")]
struct Cli {
/// The temperature in Celsius
celsius: f64,
/// How many digits to show after the decimal point
#[arg(short, long, default_value_t = 2)]
precision: usize,
/// Also print the formula that was used
#[arg(short, long)]
verbose: bool,
}
fn main() {
let cli = Cli::parse();
let fahrenheit = cli.celsius * 9.0 / 5.0 + 32.0;
println!("{} °C = {:.*} °F", cli.celsius, cli.precision, fahrenheit);
if cli.verbose {
println!("(used °F = °C × 9/5 + 32)");
}
}
Terminal window
$ cargo run -q -- 21.5
21.5 °C = 70.70 °F
$ cargo run -q -- 100 --precision 1 --verbose
100 °C = 212.0 °F
(used °F = °C × 9/5 + 32)

clap reads each field and decides what kind of argument it is:

  • celsius has no #[arg(...)] options, so it’s a positional argument: the first plain value you type. Because its type is f64, clap also parses the text "21.5" into a number for you.
  • #[arg(short, long)] makes an option with a short name and a long name taken from the field: -p / --precision. default_value_t = 2 is used when you leave it out.
  • A bool field becomes a flag: it’s true if --verbose (or -v) is present, false if not.
  • An Option<T> field (not used here) would be an argument you may leave out entirely.

{:.*} in println! means “use the next argument as the number of decimal places”. That’s how --precision reaches the output.

Cli::parse() reads the real command line. The Rust Playground has no command line, so to try the parser there, use Cli::parse_from with a list of pretend arguments. The first item is always the program’s name, just like std::env::args():

use clap::Parser;
/// Convert a temperature from Celsius to Fahrenheit
#[derive(Parser, Debug)]
#[command(name = "convert", version = "1.0")]
struct Cli {
/// The temperature in Celsius
celsius: f64,
/// How many digits to show after the decimal point
#[arg(short, long, default_value_t = 2)]
precision: usize,
/// Also print the formula that was used
#[arg(short, long)]
verbose: bool,
}
fn main() {
// Pretend the user typed: convert 100 --precision 1 --verbose
let cli = Cli::parse_from(["convert", "100", "--precision", "1", "--verbose"]);
println!("{cli:?}");
let fahrenheit = cli.celsius * 9.0 / 5.0 + 32.0;
println!("{} °C = {:.*} °F", cli.celsius, cli.precision, fahrenheit);
}

Output:

Cli { celsius: 100.0, precision: 1, verbose: true }
100 °C = 212.0 °F
▶ Try it Yourself

Try changing the pretend arguments: remove "--verbose", or swap "--precision" for "-p".

Did you notice the /// lines in the struct? Those are doc comments, and clap turns them into the help page. The comment above the struct describes the whole program; the comment above each field describes that argument. You get --help (and -h) without writing anything else:

Terminal window
$ cargo run -q -- --help
Convert a temperature from Celsius to Fahrenheit
Usage: convert [OPTIONS] <CELSIUS>
Arguments:
<CELSIUS> The temperature in Celsius
Options:
-p, --precision <PRECISION> How many digits to show after the decimal point [default: 2]
-v, --verbose Also print the formula that was used
-h, --help Print help
-V, --version Print version
$ cargo run -q -- --version
convert 1.0

The #[command(name = "convert", version = "1.0")] line sets the name and version shown here. If you write just #[command(version)], clap takes the version from your Cargo.toml instead.

clap also explains mistakes for you:

Terminal window
$ cargo run -q --
error: the following required arguments were not provided:
<CELSIUS>
Usage: convert <CELSIUS>
For more information, try '--help'.
$ cargo run -q -- abc
error: invalid value 'abc' for '<CELSIUS>': invalid float literal
For more information, try '--help'.

Good help text is a gift to your future self. Write the doc comments as if someone is seeing your tool for the first time — because one day, that someone will be you.

Now let’s support lengths too. Tools like git and cargo use subcommands (cargo build, cargo test) to group different jobs. In clap, a subcommand is an enum with #[derive(Subcommand)]. Each variant is one subcommand, and its fields are that subcommand’s arguments:

use clap::{Parser, Subcommand};
/// A small unit converter for temperatures and lengths
#[derive(Parser, Debug)]
#[command(name = "convert", version = "1.0")]
struct Cli {
/// How many digits to show after the decimal point
#[arg(short, long, default_value_t = 2, global = true)]
precision: usize,
#[command(subcommand)]
command: Command,
}
#[derive(Subcommand, Debug)]
enum Command {
/// Convert a temperature
Temp {
/// The temperature to convert
value: f64,
/// The unit to convert from
#[arg(short, long)]
from: String,
/// The unit to convert to
#[arg(short, long)]
to: String,
},
/// Convert a length
Length {
/// The length to convert
value: f64,
/// The unit to convert from
from: String,
/// The unit to convert to
to: String,
},
}
fn main() {
let cli = Cli::parse_from(["convert", "temp", "100", "--from", "c", "--to", "f"]);
println!("{cli:?}");
let cli = Cli::parse_from(["convert", "length", "5", "km", "mi", "-p", "1"]);
println!("{cli:?}");
// You find out which subcommand was used with a normal match.
match cli.command {
Command::Temp { value, from, to } => println!("temp: {value} {from} -> {to}"),
Command::Length { value, from, to } => println!("length: {value} {from} -> {to}"),
}
}

Output:

Cli { precision: 2, command: Temp { value: 100.0, from: "c", to: "f" } }
Cli { precision: 1, command: Length { value: 5.0, from: "km", to: "mi" } }
length: 5 km -> mi
▶ Try it Yourself

A few details:

  • #[command(subcommand)] on the command field tells clap “this field holds the chosen subcommand”.
  • The variant name Temp becomes the subcommand temp (clap lowercases it for you).
  • temp uses named options (--from c --to f) while length uses positional arguments (5 km mi). Both styles are common; you get to see each one.
  • global = true lets --precision appear before or after the subcommand. Without it, -p 1 would only be accepted before length.

The help page now lists the subcommands, and each subcommand has its own help:

Terminal window
$ cargo run -q -- --help
A small unit converter for temperatures and lengths
Usage: convert [OPTIONS] <COMMAND>
Commands:
temp Convert a temperature
length Convert a length
help Print this message or the help of the given subcommand(s)
Options:
-p, --precision <PRECISION> How many digits to show after the decimal point [default: 2]
-h, --help Print help
-V, --version Print version
$ cargo run -q -- temp --help
Convert a temperature
Usage: convert temp [OPTIONS] --from <FROM> --to <TO> <VALUE>
Arguments:
<VALUE> The temperature to convert
Options:
-f, --from <FROM> The unit to convert from
-p, --precision <PRECISION> How many digits to show after the decimal point [default: 2]
-t, --to <TO> The unit to convert to
-h, --help Print help

But there’s a problem. The units are plain Strings, so clap accepts anything:

Terminal window
$ cargo run -q -- temp 20 --from rankine --to c
Cli { precision: 2, command: Temp { value: 20.0, from: "rankine", to: "c" } }

We’d have to check every unit by hand later. Let’s make clap do it.

Step 5: Enums and validation with ValueEnum

Section titled “Step 5: Enums and validation with ValueEnum”

When an argument can only be one of a few words, describe those words as an enum and derive ValueEnum. clap then accepts only those words, lists them in the help, and rejects everything else. These enums replace the String fields from Step 4:

#[derive(ValueEnum, Clone, Copy, Debug, PartialEq)]
enum TempUnit {
#[value(alias = "c")]
Celsius,
#[value(alias = "f")]
Fahrenheit,
#[value(alias = "k")]
Kelvin,
}
#[derive(ValueEnum, Clone, Copy, Debug, PartialEq)]
enum LengthUnit {
Mm,
Cm,
M,
Km,
In,
Ft,
Mi,
}
  • Each variant becomes a lowercase word: Celsius → celsius, Km → km.
  • #[value(alias = "c")] also accepts the short form c, without showing it in the help.
  • clap needs Clone. We add Copy too, because these small enums are cheap to copy and that makes match easier.

In the Command enum, change from: String to from: TempUnit (and LengthUnit for lengths). We also add two more rules:

/// How many digits to show after the decimal point
#[arg(short, long, default_value_t = 2, global = true,
value_parser = clap::value_parser!(u8).range(0..=6))]
precision: u8,
/// The temperature to convert
#[arg(allow_negative_numbers = true)]
value: f64,
  • value_parser!(u8).range(0..=6) only accepts a precision from 0 to 6. More than that would just print noise.
  • allow_negative_numbers = true lets you type -40. Without it, clap thinks -40 is a short option called -4 (see Common mistakes). Temperatures can be negative, so we allow it; lengths can’t, so we don’t.

Now clap catches bad input before your code ever runs:

Terminal window
$ cargo run -q -- temp 20 --from rankine --to c
error: invalid value 'rankine' for '--from <FROM>'
[possible values: celsius, fahrenheit, kelvin]
For more information, try '--help'.
$ cargo run -q -- length 3 yd m
error: invalid value 'yd' for '<FROM>'
[possible values: mm, cm, m, km, in, ft, mi]
For more information, try '--help'.
$ cargo run -q -- -p 9 length 1 m cm
error: invalid value '9' for '--precision <PRECISION>': 9 is not in 0..=6
For more information, try '--help'.

And the help page now shows the possible values:

Terminal window
$ cargo run -q -- temp --help
Convert a temperature
Usage: convert temp [OPTIONS] --from <FROM> --to <TO> <VALUE>
Arguments:
<VALUE> The temperature to convert
Options:
-f, --from <FROM> The unit to convert from [possible values: celsius, fahrenheit, kelvin]
-p, --precision <PRECISION> How many digits to show after the decimal point [default: 2]
-t, --to <TO> The unit to convert to [possible values: celsius, fahrenheit, kelvin]
-h, --help Print help

This is the big idea of clap: once parsing succeeds, your data is already valid. from is a real TempUnit, not a string that might be anything. The rest of your program can trust it.

Some mistakes clap can’t catch, because they depend on the meaning of the values. -500 °C is a perfectly good number, but it’s colder than absolute zero (-273.15 °C), so it’s not a real temperature. That check belongs in our own code.

A tidy pattern is to put the real work in a run function that returns a Result, and let main decide what to print and which exit code to return. Programs report success or failure to the shell with a number: 0 means success, anything else means failure. Rust’s std::process::ExitCode lets main return one:

fn main() -> ExitCode {
let cli = Cli::parse();
match run(&cli) {
Ok(line) => {
println!("{line}");
ExitCode::SUCCESS // exit code 0
}
Err(message) => {
eprintln!("error: {message}"); // errors go to stderr
ExitCode::FAILURE // exit code 1
}
}
}

clap follows the same rules. When parsing fails, Cli::parse() prints the error to stderr and exits with code 2, the usual code for “you used the command wrong”. --help and --version exit with 0. In a terminal, echo $? shows the last exit code:

Terminal window
$ cargo run -q -- temp -500 -f c -t k
error: -500 °C is colder than absolute zero
$ echo $?
1
$ cargo run -q -- length 3 yd m
error: invalid value 'yd' for '<FROM>'
[possible values: mm, cm, m, km, in, ft, mi]
For more information, try '--help'.
$ echo $?
2

Why does this matter? Scripts and other programs check exit codes to know if your tool worked. The Environment and Processes page covers exit codes and stderr in more detail. In bigger tools you’d probably use anyhow for run’s error type instead of String, so you get context and error chains for free.

Here is everything together. Replace src/main.rs with this and try the commands from the top of the page.

use clap::{Parser, Subcommand, ValueEnum};
use std::process::ExitCode;
/// A small unit converter for temperatures and lengths
#[derive(Parser, Debug)]
#[command(name = "convert", version = "1.0")]
struct Cli {
/// How many digits to show after the decimal point
#[arg(short, long, default_value_t = 2, global = true,
value_parser = clap::value_parser!(u8).range(0..=6))]
precision: u8,
#[command(subcommand)]
command: Command,
}
#[derive(Subcommand, Debug)]
enum Command {
/// Convert a temperature
Temp {
/// The temperature to convert
#[arg(allow_negative_numbers = true)]
value: f64,
/// The unit to convert from
#[arg(short, long)]
from: TempUnit,
/// The unit to convert to
#[arg(short, long)]
to: TempUnit,
},
/// Convert a length
Length {
/// The length to convert
value: f64,
/// The unit to convert from
from: LengthUnit,
/// The unit to convert to
to: LengthUnit,
},
}
#[derive(ValueEnum, Clone, Copy, Debug, PartialEq)]
enum TempUnit {
#[value(alias = "c")]
Celsius,
#[value(alias = "f")]
Fahrenheit,
#[value(alias = "k")]
Kelvin,
}
#[derive(ValueEnum, Clone, Copy, Debug, PartialEq)]
enum LengthUnit {
Mm,
Cm,
M,
Km,
In,
Ft,
Mi,
}
impl TempUnit {
fn symbol(self) -> &'static str {
match self {
TempUnit::Celsius => "°C",
TempUnit::Fahrenheit => "°F",
TempUnit::Kelvin => "K",
}
}
}
impl LengthUnit {
// How many meters one of this unit is.
fn meters(self) -> f64 {
match self {
LengthUnit::Mm => 0.001,
LengthUnit::Cm => 0.01,
LengthUnit::M => 1.0,
LengthUnit::Km => 1000.0,
LengthUnit::In => 0.0254,
LengthUnit::Ft => 0.3048,
LengthUnit::Mi => 1609.344,
}
}
fn name(self) -> &'static str {
match self {
LengthUnit::Mm => "mm",
LengthUnit::Cm => "cm",
LengthUnit::M => "m",
LengthUnit::Km => "km",
LengthUnit::In => "in",
LengthUnit::Ft => "ft",
LengthUnit::Mi => "mi",
}
}
}
fn convert_temp(value: f64, from: TempUnit, to: TempUnit) -> Result<f64, String> {
// Step 1: turn whatever we got into Celsius.
let celsius = match from {
TempUnit::Celsius => value,
TempUnit::Fahrenheit => (value - 32.0) * 5.0 / 9.0,
TempUnit::Kelvin => value - 273.15,
};
if celsius < -273.15 {
return Err(format!("{value} {} is colder than absolute zero", from.symbol()));
}
// Step 2: turn Celsius into the unit we want.
Ok(match to {
TempUnit::Celsius => celsius,
TempUnit::Fahrenheit => celsius * 9.0 / 5.0 + 32.0,
TempUnit::Kelvin => celsius + 273.15,
})
}
fn convert_length(value: f64, from: LengthUnit, to: LengthUnit) -> f64 {
value * from.meters() / to.meters()
}
fn run(cli: &Cli) -> Result<String, String> {
let p = cli.precision as usize;
match cli.command {
Command::Temp { value, from, to } => {
let result = convert_temp(value, from, to)?;
Ok(format!("{value} {} = {result:.p$} {}", from.symbol(), to.symbol()))
}
Command::Length { value, from, to } => {
let result = convert_length(value, from, to);
Ok(format!("{value} {} = {result:.p$} {}", from.name(), to.name()))
}
}
}
fn main() -> ExitCode {
let cli = Cli::parse();
match run(&cli) {
Ok(line) => {
println!("{line}");
ExitCode::SUCCESS
}
Err(message) => {
eprintln!("error: {message}");
ExitCode::FAILURE
}
}
}

Two small tricks in run: {result:.p$} means “print result with p decimal places”, and the ? after convert_temp(...) returns early with the error if the temperature is impossible.

Step 8: Testing your parser with try_parse_from

Section titled “Step 8: Testing your parser with try_parse_from”

Cli::parse() exits the program when something is wrong. That’s what you want for users, but not in a test. Cli::try_parse_from(...) does the same parsing on a list of arguments you choose, and returns a Result instead of exiting. That makes it perfect for checking your parser — and it runs in the Playground:

use clap::{CommandFactory, Parser, Subcommand, ValueEnum};
/// A small unit converter for temperatures and lengths
#[derive(Parser, Debug)]
#[command(name = "convert", version = "1.0")]
struct Cli {
/// How many digits to show after the decimal point
#[arg(short, long, default_value_t = 2, global = true,
value_parser = clap::value_parser!(u8).range(0..=6))]
precision: u8,
#[command(subcommand)]
command: Command,
}
#[derive(Subcommand, Debug)]
enum Command {
/// Convert a temperature
Temp {
/// The temperature to convert
#[arg(allow_negative_numbers = true)]
value: f64,
/// The unit to convert from
#[arg(short, long)]
from: TempUnit,
/// The unit to convert to
#[arg(short, long)]
to: TempUnit,
},
/// Convert a length
Length {
/// The length to convert
value: f64,
/// The unit to convert from
from: LengthUnit,
/// The unit to convert to
to: LengthUnit,
},
}
#[derive(ValueEnum, Clone, Copy, Debug, PartialEq)]
enum TempUnit {
#[value(alias = "c")]
Celsius,
#[value(alias = "f")]
Fahrenheit,
#[value(alias = "k")]
Kelvin,
}
#[derive(ValueEnum, Clone, Copy, Debug, PartialEq)]
enum LengthUnit {
Mm,
Cm,
M,
Km,
In,
Ft,
Mi,
}
fn check(line: &str) {
println!("$ {line}");
// split_whitespace gives us the arguments, program name first.
match Cli::try_parse_from(line.split_whitespace()) {
Ok(cli) => println!(" ok: {cli:?}"),
Err(err) => {
// Only show the first line of clap's message.
let first_line = err.to_string().lines().next().unwrap_or("").to_string();
println!(" {:?}: {first_line}", err.kind());
}
}
}
fn main() {
// Panics if the parser itself is set up wrong (for example, two
// options with the same short name). Cheap insurance.
Cli::command().debug_assert();
check("convert temp -40 -f c -t fahrenheit");
check("convert length 5 km mi -p 1");
check("convert temp 20 --from rankine --to c");
check("convert -p 9 length 1 m cm");
check("convert temp 10 --to c");
check("convert --version");
}

Output:

$ convert temp -40 -f c -t fahrenheit
ok: Cli { precision: 2, command: Temp { value: -40.0, from: Celsius, to: Fahrenheit } }
$ convert length 5 km mi -p 1
ok: Cli { precision: 1, command: Length { value: 5.0, from: Km, to: Mi } }
$ convert temp 20 --from rankine --to c
InvalidValue: error: invalid value 'rankine' for '--from <FROM>'
$ convert -p 9 length 1 m cm
ValueValidation: error: invalid value '9' for '--precision <PRECISION>': 9 is not in 0..=6
$ convert temp 10 --to c
MissingRequiredArgument: error: the following required arguments were not provided:
$ convert --version
DisplayVersion: convert 1.0
▶ Try it Yourself

Notice that --version comes back as an Err too. To clap, “show the version and stop” isn’t a successful parse, so it’s reported with the kind DisplayVersion (and --help with DisplayHelp). Cli::parse() handles those for you and exits with code 0.

In your real project, the same checks go into tests. Add this to the bottom of src/main.rs:

#[cfg(test)]
mod tests {
use super::*;
use clap::CommandFactory;
#[test]
fn cli_is_set_up_correctly() {
Cli::command().debug_assert();
}
#[test]
fn parses_a_temperature() {
let cli = Cli::try_parse_from(["convert", "temp", "-40", "-f", "c", "-t", "f"]).unwrap();
assert_eq!(cli.precision, 2);
match cli.command {
Command::Temp { value, from, to } => {
assert_eq!(value, -40.0);
assert_eq!(from, TempUnit::Celsius);
assert_eq!(to, TempUnit::Fahrenheit);
}
other => panic!("expected temp, got {other:?}"),
}
}
#[test]
fn rejects_an_unknown_unit() {
let result = Cli::try_parse_from(["convert", "length", "3", "yd", "m"]);
let err = result.unwrap_err();
assert_eq!(err.kind(), clap::error::ErrorKind::InvalidValue);
}
#[test]
fn minus_forty_is_the_same_in_both() {
let f = convert_temp(-40.0, TempUnit::Celsius, TempUnit::Fahrenheit).unwrap();
assert_eq!(f, -40.0);
}
}
Terminal window
$ cargo test -q
running 4 tests
....
test result: ok. 4 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

Forgetting the derive feature. If you write cargo add clap without --features derive, the derive macros don’t exist:

error: cannot find derive macro `Parser` in this scope
--> src/main.rs:4:10
|
4 | #[derive(Parser, Debug)]
| ^^^^^^
|
note: `Parser` is imported here, but it is only a trait, without a derive macro
--> src/main.rs:1:5
|
1 | use clap::Parser;
| ^^^^^^^^^^^^
error: cannot find attribute `command` in this scope

Fix: run cargo add clap --features derive, or add features = ["derive"] in Cargo.toml by hand.

Negative numbers look like options. With the Step 2 program, try a cold day:

Terminal window
$ cargo run -q -- -40
error: unexpected argument '-4' found
tip: to pass '-4' as a value, use '-- -4'
Usage: convert [OPTIONS] <CELSIUS>
For more information, try '--help'.

clap sees - and thinks -4 is a short option. Fix: add #[arg(allow_negative_numbers = true)] to the field, like we did in Step 5. (Users can also type -- -40; everything after a lone -- is treated as a plain value.)

Calling Cli::parse() in the Playground. The Playground runs your program with no arguments, so parse() prints an error (or, for a tool with subcommands, the help page) and exits with code 2 before your code does anything. Use Cli::parse_from([...]) or Cli::try_parse_from([...]) there, and in tests.

Printing errors with println!. Error messages belong on stderr (eprintln!), not stdout. If someone runs convert ... > result.txt, the errors still show up on screen instead of hiding in the file.

  • More units. Add yards (yd) and nautical miles to LengthUnit. The compiler will point at every match you need to update.
  • A weight subcommand for kilograms, pounds and ounces.
  • A --json flag that prints the result as JSON using serde_json.
  • Read values from stdin when no value is given, so you can pipe numbers in: echo 100 | convert temp -f c -t f. An Option<f64> field tells you whether a value was typed.
  • Better errors with anyhow. Change run to return anyhow::Result<String> and use anyhow::bail!. See anyhow and thiserror.
  • Shell completions. The clap_complete crate generates tab-completion scripts for bash, zsh, fish and PowerShell from your Cli struct.
  • Add clap with cargo add clap --features derive, then put #[derive(Parser)] on a struct. Each field is one argument.
  • Plain fields are positional, #[arg(short, long)] makes options, bool makes flags, and default_value_t sets defaults. /// doc comments become the --help text.
  • #[derive(Subcommand)] on an enum gives you subcommands; #[derive(ValueEnum)] limits an argument to a fixed set of words. value_parser!(...).range(...) checks number ranges.
  • After parsing succeeds, the data is already valid. Put the remaining checks in a run function that returns Result, and return an ExitCode from main.
  • clap exits with code 2 for usage errors and 0 for --help/--version. Your own failures should use a non-zero code too, and print to stderr.
  • Cli::try_parse_from parses a list you choose and returns a Result — perfect for tests and the Playground.

Quick check

1. Which cargo add command enables #[derive(Parser)]?

2. Where does clap get the description of each option in --help?

3. What does Cli::try_parse_from(["convert", "--version"]) return?

4. A user types an invalid unit and Cli::parse() fails. Which exit code does clap use?

5. Why does convert -40 fail in the Step 2 program?

Score: 0 / 5