A grep-like CLI tool I built in Rust to learn the language properly — ownership, error handling, concurrency, all of it. It searches for text patterns across files and directories, kind of like a stripped-down ripgrep.
$ rustgrep Rust src/
src/main.rs:use std::path::Path;
src/search.rs:/// Returns true if `line` contains `query`, respecting case sensitivity.
src/cli.rs:/// A fast file search tool inspired by ripgrepGive it a pattern and a path. It'll search recursively through directories, print matching lines with filenames, and skip over anything it can't read (binary files, permission errors, etc).
| Flag | What it does |
|---|---|
-i |
Case-insensitive matching |
-n |
Show line numbers |
-c |
Highlight matches in red (ANSI colors) |
-p |
Parallel search across files using rayon |
You can combine them:
rustgrep -i -n -c -p "error" /var/log/cargo build --releaseBinary ends up in target/release/rustgrep.
src/
main.rs — entry point, wires everything together
cli.rs — argument parsing with clap
search.rs — the actual search logic, file walking, parallel mode
tests/
integration_test.rs — end-to-end tests against the binary
I kept it to three source files on purpose. cli.rs owns the config, search.rs owns the logic, main.rs just calls both and handles errors. No over-abstraction.
- Parse CLI args into a
Configstruct (clap does the heavy lifting) - Check if the target path is a file or directory
- If it's a directory, walk it recursively collecting files
- For each file, read it line-by-line with
BufReader - Check each line against the query (with
to_lowercase()for-imode) - Print matches in
file:lineformat
With -p, step 4-6 runs in parallel across all collected files using rayon's par_iter. On a directory with 500 small files, this gave roughly a 1.2x speedup in benchmarks — not huge for tiny files, but the gap widens with larger codebases.
No unwrap() calls in the final code. Everything returns Result, errors propagate with ?, and main() is the only place that prints error messages and sets the exit code. Files that fail to open just get a stderr warning and the search keeps going — same behavior as real grep.
cargo test9 unit tests covering search_line and highlight_match edge cases (case sensitivity, empty strings, no-match scenarios). 9 integration tests that spawn the actual binary and check stdout/stderr/exit codes for each feature.
- Rust's ownership model clicked once I had to pass
&Configthrough multiple function layers without cloning - The difference between
Stringand&strstops being confusing once you think "who owns this data?" ?operator makes error handling feel lightweight instead of painful- rayon is almost cheating — changing
.iter()to.par_iter()and everything just works because&Configis alreadySend + Sync BufReadermatters — reading files line-by-line without it would be painfully slow