Rust
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Ownership and Borrowing
-
Section 4: Structuring Data
-
Section 5: Collections and Error Handling
-
Section 6: Traits and Generics
-
Section 7: Concurrency
-
Section 8: Building for the Web
-
Section 9: Memory and Performance
-
Section 10: More Standard Library and Ecosystem
-
Section 11: Advanced Rust
-
Section 12: Rust for Systems and WebAssembly
-
Section 13: Tooling and Best Practices
-
Section 14: Data Structures and Algorithms in Rust
-
Section 15: Practical Projects
-
Section 16: Interview Practice
-
Section 17: std::collections In Depth
-
Section 18: std::io and std::fs In Depth
-
Section 19: std::net
-
Section 20: std::option and std::result In Depth
-
Section 21: std::iter In Depth
-
Section 22: std::sync In Depth
-
Section 23: std::string and std::str
-
Section 24: Cargo and Crates.io Ecosystem
-
Section 25: Popular Crates Ecosystem
-
Section 26: Rust Memory Model Deep Dive
-
Section 27: More Practice Exercises
-
Section 28: More Interview Practice
-
Section 29: Async Rust Deep Dive
-
Section 30: Tokio Ecosystem In Depth
-
Section 31: Error Handling Ecosystem Deep Dive
-
Section 32: Serde In Depth
-
Section 33: Web Development with Rust Deep Dive
-
Section 34: Database Access Ecosystem
-
Section 35: Rust for Embedded Systems Deep Dive
-
Section 36: Rust Macros In Depth
-
Section 37: Advanced Trait System
-
Section 38: Unsafe Rust In Depth
-
Section 39: Rust CLI Development
-
Section 40: Testing Ecosystem Deep Dive
-
Section 41: WebAssembly Deep Dive
-
Section 42: Rust Design Patterns
-
Section 43: More Data Structures in Rust
-
Section 44: Final Practice Projects
-
Section 45: Rust Performance Optimization
-
Section 46: Rust Ecosystem Tooling
-
Section 47: More Interview and Review
202: Building CLIs with Clap Derive API
By now, you've probably realized that parsing std::env::args() manually is a nightmare the moment you need more than one flag. That's why we use clap. While there's a builder API, the Derive API is where the real magic happens—it lets you define your CLI interface as a plain Rust struct, and clap handles the rest via macros.
How do I actually map a struct to command-line arguments?
First, you need to make sure you have the derive feature enabled in your Cargo.toml, otherwise the Parser trait won't be available. I've seen plenty of people spend an hour debugging "trait not found" errors only to realize they forgot the feature flag.
[dependencies]
clap = { version = "4.0", features = ["derive"] }
Let's say we're building a tool called log-scan that searches for specific keywords in a log file. You just define a struct and decorate it. I usually keep the struct name simple, like Cli or Args.
use clap::Parser;
#[derive(Parser)]
#[command(name = "log-scan")]
#[command(about = "A simple tool to sift through logs", long_about = None)]
struct Cli {
/// The path to the log file to analyze
path: std::path::PathBuf,
/// The keyword to search for
#[arg(short, long)]
query: String,
/// Number of lines of context to show
#[arg(short, long, default_value_t = 2)]
context: usize,
}
fn main() {
let cli = Cli::parse();
println!("Searching for {} in {:?} with {} lines of context",
cli.query, cli.path, cli.context);
}
Notice the doc comments (///). clap actually parses those and turns them into the --help text automatically. It's a great way to keep your documentation and your CLI interface in one place.
What if I need different "modes" of operation, like subcommands?
If your tool does more than one distinct thing, don't try to jam it all into a few flags. That's where subcommands come in. In the Derive API, you represent subcommands as an enum.
Let's expand log-scan so it can either search for a term or count the number of errors. Here is how I'd structure that:
use clap::{Parser, Subcommand};
#[derive(Parser)]
struct Cli {
#[command(subcommand)]
command: Commands,
}
#[derive(Subcommand)]
enum Commands {
/// Search for a specific pattern in the logs
Search {
query: String,
#[arg(short, long)]
case_insensitive: bool,
},
/// Count occurrences of a specific log level (e.g., ERROR, WARN)
Count {
level: String,
},
}
fn main() {
let cli = Cli::parse();
match &cli.command {
Commands::Search { query, case_insensitive } => {
println!("Searching for {} (case-insensitive: {})", query, case_insensitive);
}
Commands::Count { level } => {
println!("Counting logs with level: {}", level);
}
}
}
This pattern is incredibly powerful because it forces you to handle the logic for each command explicitly using a match statement, which is exactly how Rust's type system is meant to be used.
How do I handle optional arguments without everything becoming an Option<T>?
You'll notice that if you make a field an Option<String>, clap treats it as optional. That's fine, but often you want a sensible default instead of having to unwrap_or() every single variable in your main function.
You can use the default_value_t attribute for types that implement Display, or default_value for raw strings. Honestly, I prefer default_value_t because it's type-safe.
#[derive(Parser)]
struct Cli {
/// Output format: json or text
#[arg(short, long, default_value = "text")]
format: String,
/// Max threads to use for scanning
#[arg(short, long, default_value_t = 4)]
threads: usize,
}
One more tip: if you want a flag that is just a boolean "on/off" switch (like --verbose), just use bool. clap knows that if a field is a bool, it shouldn't expect a value after the flag—its presence alone sets it to true.
📋 Practical Task
Exercise: Build a "Project File Auditor"
Your task is to create a CLI tool called audit-tool that helps a developer find specific files in a project directory. The tool must meet the following requirements:
- The Main Struct: Must take a positional argument for the
root_dir(aPathBuf). - Subcommands: Implement two subcommands:
find-large: Takes an optional argumentsize_mb(au64) which defaults to 100. It should print: "Searching for files larger than [size]MB in [root_dir]".check-ext: Takes a required argumentextension(aString) and a boolean flagrecursive. It should print: "Checking for .[extension] files in [root_dir] (Recursive: [true/false])".
Ensure you use the derive feature of clap and implement the logic in main using a match statement to handle the subcommands.
There are no comments for now.