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
php
<?php declare(strict_types=1);
A single-action JSON user controller in PHP
invokable-class
json-api
validation
Intermediate
7 steps
rust
use std::sync::Arc; use axum::{ body::Body, extract::{Extension, State},
Passing per-request context through Axum middleware
middleware
request-context
dependency-injection
Intermediate
8 steps
rust
use std::collections::HashMap; use std::fmt; #[derive(Debug)]
Parsing a config file with typed errors in Rust
error-handling
enums
parsing
Intermediate
9 steps
java
public final class ShippingAddress { private final String recipient; private final String line1; private final String line2;
Building immutable objects with the Builder pattern
builder-pattern
immutability
validation
Intermediate
7 steps
rust
use chrono::{DateTime, Duration, FixedOffset, Utc}; #[derive(Debug)] pub struct EventWindow {
Parsing and measuring time windows in Rust
datetime
error-handling
pattern-matching
Intermediate
7 steps
javascript
export function buildPaginationUrl(baseUrl, { page, perPage, filters = {}, sort } = {}) { const url = new URL(baseUrl); const params = url.searchParams;
Building and parsing pagination URLs
url-parsing
query-string
pagination
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.