Why Rust is the ideal choice for CLI tools?
\n\nHave you ever wondered why all modern DevOps tools — from bat to ripgrep and fd — are written in Rust? It's not just a trend. CLI utilities in Rust are incredibly fast, safe, and best of all, they can be compiled into a single tiny binary with no dependencies. No "do you have Java installed?" or "wait while Node.js pulls in node_modules". Just download and you're done.
In this article, we will create a CLI utility in Rust from scratch. It won't be an abstract "Hello, World!" but a real useful tool — a log analyzer that searches for errors, counts them, and outputs results in a beautiful colored format. And yes, we'll do it in one evening.
\n\nWhat do we need?
\n\nTo start, you need a minimum:
\n- \n
- Rust (install via
rustupif you don't have it yet) \n - Any code editor (VS Code with the rust-analyzer plugin is ideal) \n
- A couple of hours of free time \n
If you've already written in Python or Go, Rust may seem unusual but logical. The main thing is not to be afraid of the compiler. It's not mean, it's just caring.
\n\nStep 1: Create the project
\n\nOpen the terminal and type:
\ncargo new log-scanner\ncd log-scanner\n\nCargo is our Swiss army knife. It created a basic structure with Cargo.toml (dependency file) and src/main.rs (entry point). You can already run cargo run and see "Hello, world!". But we'll go further.
Open Cargo.toml and add the dependencies that will turn our code into a real CLI utility in Rust:
[dependencies]\nclap = { version = "4.5", features = ["derive"] }\ncolored = "2.1"\nanyhow = "1.0"\n\n- \n
- clap — command-line argument parsing (can't do without it) \n
- colored — colored terminal output \n
- anyhow — convenient error handling \n
Step 2: Define the argument structure
\n\nThe coolest thing about clap is the derive macros. You simply describe the structure, and the magic happens by itself. Here's what it looks like:
use clap::Parser;\n\n#[derive(Parser)]\n#[command(name = "log-scanner")]\n#[command(about = "Scans log files and searches for errors")]\nstruct Cli {\n /// Path to the log file\n file: String,\n\n /// String to search for (default "ERROR")\n #[arg(short, long, default_value = "ERROR")]\n pattern: String,\n\n /// Show only the count of found lines\n #[arg(short, long)]\n count: bool,\n}\n\nNote: comments with three slashes /// automatically become hints in --help. Run cargo run -- --help — and you'll see a neatly generated help page. This is one of the reasons why creating CLI in Rust is so pleasant: documentation writes itself.
Step 3: Write the search logic
\n\nNow for the interesting part — reading the file and searching for matches. Rust is famous for its safe file and memory handling, but in practice it's just convenient:
\n\nuse std::fs::File;\nuse std::io::{BufRead, BufReader};\nuse colored::*;\nuse anyhow::Result;\n\nfn scan_log(file_path: &str, pattern: &str, count_only: bool) -> Result<()> {\n let file = File::open(file_path)?;\n let reader = BufReader::new(file);\n let mut total_matches = 0;\n\n for (line_num, line) in reader.lines().enumerate() {\n let line = line?;\n if line.contains(pattern) {\n total_matches += 1;\n if !count_only {\n println!(\n "{}:{} {}",\n format!("{:>4}", line_num + 1).blue(),\n patt