Overview
What This Tutorial Teaches
This tutorial teaches Rust CLI development through 80 heavily annotated, self-contained code examples. The target audience is engineers who already know Java, Python, Go, or JavaScript/TypeScript well and want to read and write real Rust CLI codebases without getting stuck on Rust's unique concepts.
After completing all 80 examples, you can read any production Rust CLI tool—including real projects like ripgrep, fd, cargo, and bat—and understand every line. You can write your own CLIs that handle files, directories, serialization, error propagation, and testable output.
This tutorial focuses exclusively on synchronous Rust. Async/await is a separate, steep learning curve that belongs in a dedicated tutorial. All 80 examples compile and run with stable Rust.
Why This Tutorial Exists
The three biggest struggles for GC-language engineers learning Rust are:
-
Ownership and borrowing — 8 full examples use CLI-domain scenarios (processing command names, reading config paths, accumulating validation results) rather than toy memory examples that feel disconnected from real work.
-
Lifetimes — This tutorial deliberately avoids teaching lifetime annotations. Every example uses owned types (
String,Vec<T>,HashMap) so the borrow checker never demands annotations. You build intuition for ownership without the additional complexity of annotation syntax. -
Type system and trait thinking — Each trait example explicitly contrasts Rust traits with Java interfaces (nominal subtyping) and Go interfaces (structural duck-typing). Rust uses structural dispatch like Go but with explicit
impldeclarations like Java.
Edition and Toolchain
All examples target Rust 2024 edition (stable since Rust 1.85). Important Edition 2024 changes reflected throughout:
genis a reserved keyword — no example uses it as an identifierstatic mutrequiresunsafe— examples useLazyLockandOnceLockinsteadstd::env::set_varisunsafe— examples avoid it or wrap properly- Match ergonomics improved — examples follow current idioms
Key Crates
Every crate used in this tutorial is stable, widely adopted, and production-standard:
| Crate | Version | Purpose |
|---|---|---|
clap | 4.6.x | CLI argument parsing with derive API |
anyhow | 1.x | Error handling for CLI applications |
serde | 1.x | Serialization framework |
serde_json | 1.x | JSON serialization |
serde_yml | 0.0.x | YAML serialization |
walkdir | 2.x | Simple directory traversal |
ignore | 0.4.x | Gitignore-aware directory walking |
regex | 1.x | Regular expressions |
assert_cmd | 2.x | CLI integration testing |
tempfile | 3.x | Temporary files in tests |
Learning Path
%% Color Palette: Blue #0173B2, Orange #DE8F05, Teal #029E73, Purple #CC78BC, Brown #CA9161
graph TD
A["Beginner<br/>Examples 1-28<br/>Rust Fundamentals via CLI Lens"] --> B["Intermediate<br/>Examples 29-57<br/>Real CLI Patterns"]
B --> C["Advanced<br/>Examples 58-80<br/>Production CLI"]
style A fill:#0173B2,color:#fff
style B fill:#DE8F05,color:#fff
style C fill:#029E73,color:#fff
Beginner (Examples 1-28): Rust Fundamentals via CLI Lens
Focus: Learn Rust fundamentals using CLI-domain examples throughout.
Every ownership example involves a command name, config path, or results accumulator—not abstract toy data. By example 28 you understand ownership, borrowing, traits, enums with data, pattern matching, Option<T>, Result<T, E>, the ? operator, Vec<T>, HashMap<K, V>, iterators, closures, and a first working CLI with clap.
Key topics: fn main(), variables, shadowing, types, String vs &str, functions, ownership, borrowing, mutable borrowing, slices, structs, impl blocks, #[derive], enums, enums with data, pattern matching, if let, Option, Result, ?, Vec, HashMap, iterators, closures, traits, first clap CLI, module system, unit tests.
Intermediate (Examples 29-57): Real CLI Patterns
Focus: The patterns that appear in every production Rust CLI.
Subcommands, global flags, file I/O, directory walking, regex, lazy globals, anyhow error handling, serde serialization, BTreeMap for deterministic output, testable output via dyn Write, environment variables, exit codes, output format enums, advanced iterators, and integration testing with assert_cmd and tempfile.
Key topics: Clap subcommands, nested subcommands, global flags, string manipulation, PathBuf/Path, file reading/writing, walkdir, ignore crate, regex, LazyLock, OnceLock, anyhow, error chains, serde JSON/YAML, BTreeMap, struct constructors, result accumulation, impl Into<String>, dyn Write, env vars, exit codes, output formats, iterator chaining, vec operations, integration testing.
Advanced (Examples 58-80): Production CLI
Focus: The patterns that distinguish amateur from professional Rust CLI codebases.
Module organization, type aliases, Clippy configuration, restriction lints, replacing .unwrap(), custom Display/FromStr, markdown/XML parsing, glob patterns, SHA-2 hashing, compiled regex caches, validation orchestration, release profiles, dual crate layout, match guards, recursive validation, chrono dates, avoiding common pitfalls, complex test fixtures, and a final capstone example synthesizing all concepts.
Key topics: Module organization, type aliases, Clippy, restriction lints, replacing unwrap, Display/FromStr, pulldown-cmark, quick-xml, glob, sha2, OnceLock regex cache, validation orchestration, integration testing assertions, testing stderr, release profiles, dual crate, match guards, recursive validation, chrono, common pitfalls, complex fixtures, capstone CLI.
How to Use This Tutorial
Create a Cargo Project
Most beginner examples are single-file. Create one project for all examples:
cargo new rust-cli-examples && cd rust-cli-examplesFor examples that use external crates, add to Cargo.toml:
[dependencies]
clap = { version = "4.6.1", features = ["derive"] }
anyhow = "1.0"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
serde_yml = "0.0.12"
walkdir = "2.5"
ignore = "0.4"
regex = "1.11"
[dev-dependencies]
assert_cmd = "2.0"
tempfile = "3.14"
predicates = "3.1"Read the Annotations
Every code block uses // => comments to show values, outputs, ownership transfers, and error conditions:
let name = String::from("my-tool"); // => name owns heap string "my-tool"
let borrowed: &str = &name; // => borrowed is a view into name's data
// => name still owns the string
println!("{}", borrowed); // => Output: my-toolFollow the Progression
Start with Beginner even if you know other systems languages. Rust's ownership model is unique and requires building intuition from real CLI scenarios. Skipping ahead leaves gaps that cause confusion when the borrow checker rejects code that looks correct.
Five-Part Example Format
Every example uses this structure:
- Brief explanation (2-4 sentences): what the concept is and why it matters for CLI tools
- Mermaid diagram (when the concept benefits from visualization): ownership flows, module structure, error chains
- Heavily annotated code: every significant line has
// =>comments showing state, values, and outputs - Key Takeaway: the core insight in 1-2 sentences
- Why It Matters: production relevance and connection to real CLI codebases
Prerequisites
Required: Experience with at least one of Java, Python, Go, or JavaScript/TypeScript. Ability to run cargo new and cargo run.
Not required: Prior Rust experience, systems programming background, understanding of memory management.
Examples by Level
Beginner (Examples 1-28)
- Example 1: Hello World CLI
- Example 2: Variables and Mutability
- Example 3: Variable Shadowing
- Example 4: Basic Types
- Example 5: String Types
- Example 6: Functions
- Example 7: Ownership Basics
- Example 8: Borrowing
- Example 9: Mutable Borrowing
- Example 10: Slices
- Example 11: Structs
- Example 12: Struct impl Blocks
- Example 13: Derive Macros
- Example 14: Enums
- Example 15: Enums with Data
- Example 16: Pattern Matching
- Example 17: if let and while let
- Example 18: Option
- Example 19: Result
- Example 20: The Question Mark Operator
- Example 21: Vec
- Example 22: HashMap
- Example 23: Iterators
- Example 24: Closures
- Example 25: Traits
- Example 26: First CLI with clap
- Example 27: Module System
- Example 28: Unit Tests
Intermediate (Examples 29-57)
- Example 29: Clap Subcommands
- Example 30: Nested Subcommands
- Example 31: Global Flags with clap
- Example 32: String Manipulation
- Example 33: String Formatting
- Example 34: PathBuf and Path
- Example 35: Reading Files
- Example 36: Writing Files
- Example 37: Walking Directories
- Example 38: Gitignore-Aware Walking
- Example 39: Regex Basics
- Example 40: std::sync::LazyLock
- Example 41: std::sync::OnceLock
- Example 42: Error Handling with anyhow
- Example 43: Error Propagation Chain
- Example 44: Serde and JSON
- Example 45: Serde and YAML
- Example 46: BTreeMap
- Example 47: Struct Constructors Pattern
- Example 48: Collecting Results
- Example 49: impl Into String
- Example 50: dyn Write for Testable Output
- Example 51: Environment Variables
- Example 52: Process Exit Codes
- Example 53: Output Format Enum
- Example 54: Iterator Advanced
- Example 55: Vec Operations
- Example 56: Testing with assert_cmd
- Example 57: Testing with tempfile
Advanced (Examples 58-80)
- Example 58: Module Organization
- Example 59: Type Aliases
- Example 60: Clippy Basics
- Example 61: Clippy Configuration in Cargo.toml
- Example 62: Restriction Lints
- Example 63: Replacing unwrap
- Example 64: Custom Display for Enums
- Example 65: Markdown Parsing
- Example 66: XML Parsing
- Example 67: Glob Patterns
- Example 68: SHA-2 Hashing
- Example 69: OnceLock and Regex Cache
- Example 70: Validation Orchestration
- Example 71: Integration Testing with assert_cmd and predicates
- Example 72: Testing stderr
- Example 73: Release Profile
- Example 74: Dual Crate Layout
- Example 75: Complex Match Guards
- Example 76: Recursive Directory Validation
- Example 77: chrono for Dates
- Example 78: Avoiding Common Rust Pitfalls
- Example 79: Testing with Complex Fixtures
- Example 80: Putting It All Together
Last updated December 29, 2025