rust
52 lines · 10 steps
Building a CLI with clap's derive macros
clap turns annotated Rust structs and enums into a full command-line parser with subcommands, validation, and defaults.
Explained by
highlit
1use std::path::PathBuf;
2
3use clap::{Parser, Subcommand, ValueEnum};
4
5#[derive(Parser)]
6#[command(name = "imgtool", version, about = "Batch image processor")]
7struct Cli {
8 #[arg(short, long, global = true, action = clap::ArgAction::Count)]
9 verbose: u8,
10
11 #[arg(long, value_name = "FILE", global = true)]
12 config: Option<PathBuf>,
13
14 #[command(subcommand)]
15 command: Command,
16}
17
18#[derive(Subcommand)]
19enum Command {
20 Convert {
21 #[arg(required = true)]
22 inputs: Vec<PathBuf>,
23
24 #[arg(short, long)]
25 output_dir: PathBuf,
26
27 #[arg(short, long, value_enum, default_value_t = Format::Png)]
28 format: Format,
29
30 #[arg(short, long, value_parser = clap::value_parser!(u8).range(1..=100), default_value_t = 85)]
31 quality: u8,
32 },
33 Resize {
34 input: PathBuf,
35
36 #[arg(short = 'W', long)]
37 width: Option<u32>,
38
39 #[arg(short = 'H', long)]
40 height: Option<u32>,
41
42 #[arg(long, conflicts_with_all = ["width", "height"])]
43 scale: Option<f32>,
44 },
45}
46
47#[derive(Copy, Clone, ValueEnum)]
48enum Format {
49 Png,
50 Jpeg,
51 Webp,
52}
01 / 01
STEP 01
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1Deriving Parser and Subcommand lets clap generate parsing, help, and validation from your type definitions.
- 2Attribute options like global, conflicts_with_all, and value_parser encode CLI rules declaratively instead of hand-written checks.
- 3Modeling optional flags as Option and choices as ValueEnum makes invalid states hard to represent.
Related explainers
rust
use std::time::{Duration, Instant}; pub struct Ewma { alpha: f64,
A time-decayed moving average in Rust
exponential-smoothing
time-decay
state-machine
Intermediate
8 steps
rust
use std::fs::File; use std::io::{Read, Seek, SeekFrom}; #[derive(Debug)]
Parsing an HTTP Range header in Rust
http-range
parsing
file-io
Intermediate
10 steps
java
public record AppConfig( String host, int port, String databaseUrl,
Loading typed config from env vars in Java
records
configuration
environment-variables
Intermediate
6 steps
rust
use rand::distributions::{Alphanumeric, DistString}; use rand::rngs::OsRng; #[derive(Debug, Clone, PartialEq, Eq)]
A newtype wrapper for API tokens in Rust
newtype-pattern
randomness
encapsulation
Intermediate
6 steps
php
<?php namespace App\Validation;
Building a reusable address form validator in PHP
validation
error-accumulation
regex
Intermediate
9 steps
rust
pub fn normalize_path(input: &str) -> String { let is_absolute = input.starts_with('/'); let has_trailing_slash = input.len() > 1 && input.ends_with('/'); let mut stack: Vec<&str> = Vec::new();
Normalizing filesystem paths in Rust
string-processing
stack
path-manipulation
Intermediate
8 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/building-a-cli-with-clap-s-derive-macros-explained-rust-4e73/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.