CLI utilities in Rust: how to write your first tool in an evening

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

Why Rust is the ideal choice for CLI tools?

\n\n

Have 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.

\n\n

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\n

What do we need?

\n\n

To start, you need a minimum:

\n
    \n
  • Rust (install via rustup if 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
\n\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\n

Step 1: Create the project

\n\n

Open the terminal and type:

\n
cargo new log-scanner\ncd log-scanner
\n\n

Cargo 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.

\n\n

Open Cargo.toml and add the dependencies that will turn our code into a real CLI utility in Rust:

\n
[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
\n\n

Step 2: Define the argument structure

\n\n

The 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:

\n\n
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\n

Note: 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.

\n\n

Step 3: Write the search logic

\n\n

Now 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\n
use 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                

Blogs

Book Recommendations