32blogby Studio Mitsu

Why Rust CLI Tools Are So Fast: ripgrep, fd, and the uutils Era

Rust CLIs like ripgrep and fd beat grep and find thanks to SIMD, zero-cost abstractions, Rayon, and .gitignore awareness. The full technical picture.

by omitsu15 min read
On this page

Rust CLI tools like ripgrep, fd, and bat are faster than their classic UNIX counterparts for four concrete reasons: SIMD-accelerated byte scanning (the Teddy algorithm), zero-cost abstractions that compile high-level code down to C-speed machine instructions, Rayon-powered work-stealing parallelism that saturates every CPU core, and automatic .gitignore skipping that avoids touching files you never wanted to search anyway. On the Linux kernel source tree, ripgrep runs roughly 9x faster than GNU grep; inside a typical Node.js repo with node_modules on disk, the gap can stretch past 300x.

You already noticed it. You ran rg TODO inside a monorepo and it came back before your fingers left the keyboard. Then out of habit you tried grep -r TODO ., went to get coffee, and it was still crawling when you came back. The question that brings people here is the obvious one: why is the Rust version this much faster, and is it always faster?

The short answer is "yes, almost always" — but the interesting part is why, because the reasons aren't magic. Rust did not reinvent searching or filesystem walking. It combined four specific engineering decisions that classic coreutils would need a rewrite to match. That rewrite is actually happening — the uutils/coreutils project shipped as the default in Ubuntu 25.10 and continues in Ubuntu 26.04 LTS.

If you want the hands-on guide to installing these tools and aliasing them into your shell, start with the Modern Rust CLI Tools roundup. This article is the why behind the speed.

The four real reasons Rust CLIs outpace their predecessors

Before diving in, it helps to know what we are not going to say. Rust is not faster than C. A C program that uses the same algorithms and the same SIMD intrinsics would be equally fast — and historically, GNU grep has been extraordinarily well-tuned C code. The reason ripgrep beats GNU grep is not that Rust is a magical performance language. It is that the ripgrep authors implemented a better set of algorithms, and Rust made those algorithms easy to write safely.

Here are the four real reasons, each of which we will unpack in its own section below.

  1. SIMD byte scanning via the Teddy algorithm and memchr — scanning 16 to 32 bytes per cycle instead of one byte at a time.
  2. Zero-cost abstractions — high-level iterator and generic code compiles to the same machine code you would write by hand in C, with no garbage collector or runtime overhead.
  3. Rayon work-stealing parallelism — filesystem walks and searches fan out across every CPU core with a few lines of code, something classic coreutils almost never do.
  4. .gitignore awareness — real repositories contain enormous amounts of generated junk (node_modules, target, dist, .venv). Skipping them is not an optimization, it is a correctness fix.

On the Linux kernel source tree, a recent benchmark had ripgrep running about 9.2x faster than GNU grep when searching for EXPORT_SYMBOL, and about 11.8x faster when searching a regex like spin_lock.*irq. In a Node.js project with node_modules present on disk, ripgrep finishes around 302x faster than grep with default flags — and is still 21x faster even when grep is manually told to skip node_modules.

SIMD and the Teddy algorithm: scanning 16 bytes at a time

The single biggest technical reason Rust CLI tools pull ahead is SIMD — Single Instruction, Multiple Data. Modern x86-64 and ARM CPUs can operate on 128-bit or 256-bit vector registers, meaning a single instruction can compare 16 or 32 bytes at once. Traditional byte-at-a-time C loops only process one character per cycle. SIMD code processes sixteen. That is the gap.

Rust's regex crate — the same engine ripgrep uses — leans hard on a SIMD algorithm called Teddy, originally developed by Geoffrey Langdale as part of Intel's Hyperscan regex library. Teddy is not a general-purpose regex engine. It is a specialized prefilter that finds candidate positions in a haystack where a set of literal strings might match, using packed byte comparisons. Only when a candidate hit is found does the slower full regex engine run.

In practice, this means ripgrep scans most of the file at roughly memory bandwidth, only stopping to "think" when it hits something that looks interesting. You can read the technical breakdown in BurntSushi's deep dive on ripgrep's performance.

On top of Teddy, ripgrep uses the memchr crate, a SIMD-accelerated byte search primitive that examines 16 bytes per loop iteration. Line counting — one of the hidden costs in any grep — uses packed comparisons to count newlines 16 bytes at a time instead of one byte at a time.

bash
# On a fresh Linux kernel clone, compare real-world speed
hyperfine --warmup 2 \
  'grep -r "EXPORT_SYMBOL" linux/' \
  'rg "EXPORT_SYMBOL" linux/'

If you install hyperfine (also Rust-powered) and run the command above, the ratio you see will vary with your CPU and storage, but ripgrep will consistently land in the 5x–15x faster range depending on the workload.

Why GNU grep doesn't just do this

GNU grep is a legendary piece of C code. It does use some SIMD, and it is shockingly fast on the single-file ASCII case. But it carries decades of POSIX compatibility baggage, supports legacy locales, and cannot easily adopt the Teddy algorithm without a major rewrite. ripgrep had the luxury of starting fresh in 2016 and targeting modern developer workflows — Unicode by default, UTF-8 by default, .gitignore by default.

Zero-cost abstractions: how Rust stays both safe and fast

Here is the part that trips people up. Rust has iterators, closures, traits, generics, Option, Result, and all the high-level constructs you would find in a scripting language. Yet the compiled output runs at C speed. How?

The answer is monomorphization plus LLVM optimization. When you write a generic function in Rust, the compiler generates one specialized copy per concrete type at compile time. There is no dynamic dispatch, no vtable lookup, no type erasure. By the time the machine code is produced, the "abstraction" has evaporated. What's left is exactly the code you would have written by hand.

Concretely, this is what it means for CLI tools:

  • iter().filter().map().collect() compiles to the same loop you would write in C.
  • Option<T> and Result<T, E> have zero runtime cost — they are represented as tagged unions and inlined away.
  • Box<T> and Rc<T> only allocate when you actually use them; there is no hidden runtime.
  • No garbage collector pauses. Memory is freed the instant the owning variable goes out of scope.

Compare this to Python, Ruby, or Node.js tools. Every function call crosses a VM boundary, every object sits behind a layer of indirection, and the GC can pause for tens of milliseconds at unpredictable times. That is why tools written in scripting languages — even good ones like ack (Perl) or ag (C, but I/O-bound) — cannot keep up with Rust CLIs on large corpora.

Rayon and fearless concurrency: parallel filesystem walks

The third reason is the easiest to explain and probably the most impactful for directory-walking tools like fd and recursive search in ripgrep.

Rayon is a Rust library that lets you turn any sequential iterator into a parallel one by changing .iter() to .par_iter(). It uses a work-stealing thread pool under the hood: idle threads grab work from busy threads, keeping every core saturated without you writing a single line of locking code.

Rust's ownership system guarantees at compile time that your parallel code has no data races. The term "fearless concurrency" is not marketing — the borrow checker literally refuses to compile code that would corrupt shared state. For a CLI tool that wants to walk millions of files across 16 cores, this turns a hair-pulling threading problem into a one-line change.

fd, the Rust find replacement, uses this to parallelize directory traversal. In real benchmarks on large trees, fd is typically 5x–9x faster than find -iname and 9x–23x faster than find -iregex depending on the filesystem and the pattern. The underlying directory walker is walkdir (sequential) or the ignore crate (parallel + gitignore-aware), both maintained by the ripgrep author.

bash
# fd vs find, real-world comparison
hyperfine --warmup 2 \
  'find /usr -iname "*.so"' \
  'fd -e so . /usr'

Gitignore awareness: the unfair advantage in real repos

This is the reason casual benchmarks show ripgrep at "300x faster" while deeper benchmarks show "9x faster." Both numbers are correct, they just measure different things.

The truth is that real repositories contain staggering amounts of machine-generated content. A fresh npm install drops 200MB of JavaScript into node_modules. A Rust project compiles into hundreds of megabytes of .rlib and .o files under target/. A Python virtualenv hides a full Python installation under .venv/. Classic grep -r dutifully searches all of it. You did not want that. You never wanted that.

ripgrep and fd read your .gitignore, .ignore, and .rgignore files automatically and skip anything matched. The result is that they are not only scanning faster, they are scanning far less data. On a typical Node.js monorepo, grep -r might touch 2GB of files while rg touches 80MB. Even if both tools had identical scan speed, ripgrep would still win by an order of magnitude just by reading less.

This is why the "302x" benchmark on node_modules-heavy repos feels almost unfair. It is not a pure algorithmic comparison — it is a comparison between "search everything" and "search what a developer actually cares about." For day-to-day work, the .gitignore-aware number is the honest one, because it reflects what you actually experience.

If you genuinely need to search files that .gitignore would normally exclude, pass rg --no-ignore or rg -uuu (three levels of "unrestricted") — the default is opinionated, not locked. You can read more about the ripgrep .gitignore design decisions in the project FAQ. The key insight from Andrew Gallant, ripgrep's author, is that developer tools should default to developer expectations.

uutils and Ubuntu 26.04: Rust becomes the new default

The story above is about new tools that sit alongside grep, find, and cat. But there is a parallel story where Rust is quietly replacing the classics outright.

The uutils/coreutils project is a Rust reimplementation of the GNU coreutils — ls, cp, mv, cat, date, rm, and roughly 100 other foundational commands. In November 2025, Ubuntu 25.10 shipped uutils as the default coreutils implementation, a first for any major Linux distribution. Ubuntu 26.04 LTS "Resolute Raccoon" continues that choice and will be the first Long Term Support release to ship Rust coreutils by default when it launches on April 23, 2026.

At the current version (uutils 0.8.0), the project passes approximately 88% of the GNU coreutils compatibility test suite. For end users, the transition is invisible — commands work the same, flags behave the same, output looks the same. The motivations behind the switch are not primarily about performance (GNU coreutils are already extremely fast C) but about memory safety: entire categories of buffer overflows, use-after-free, and integer overflow bugs are impossible to write in safe Rust.

Sylvestre Ledru, the lead maintainer of uutils, presented the current state of the project at FOSDEM 2026, including honest numbers on where Rust coreutils is slower than GNU (some specialized tools like sort and cp still lag in certain workloads) and where it is already faster or equal.

If you are running Ubuntu 25.10 or later, try this:

bash
# Check which coreutils implementation you have
which ls
ls --version | head -n 1

On uutils, ls --version outputs ls (uutils coreutils) 0.8.0 (or whatever version your distribution ships). On GNU, it outputs ls (GNU coreutils) 9.x. Either one works — your shell scripts do not care.

FAQ

Is Rust actually faster than C for CLI tools?

No, and that is the wrong question. A well-written C program using the same algorithms will be as fast as the Rust version. The reason Rust CLI tools beat C CLI tools is that the Rust versions were written recently with modern algorithms (SIMD, work-stealing parallelism, .gitignore awareness), while the C versions were written decades ago and carry compatibility baggage. Rust's real contribution is making those modern algorithms easier to write safely.

Why not just rewrite GNU grep with SIMD?

People have tried. GNU grep does use some SIMD for specific cases, but its POSIX compliance requirements, its support for legacy locales and character encodings, and its enormous installed base make a full Teddy-style rewrite extremely risky. ripgrep had the freedom to drop compatibility in exchange for a cleaner design targeted at modern developer workflows.

Does .gitignore skipping ever cause problems?

Yes. If you need to find a file that your .gitignore excludes — say, a secret accidentally committed into node_modules — you must explicitly pass --no-ignore (or -uuu for the nuclear option). Silent skipping is opinionated and occasionally surprising. This is a real trade-off, and some sysadmin workflows prefer the blunt behavior of classic find and grep for exactly this reason.

Should I replace grep and find everywhere in my shell scripts?

No. Use POSIX-compliant grep, find, and cat in shell scripts that need to run on any machine. Use ripgrep, fd, and bat for interactive work at the command line. This is the same split recommended in the Modern Rust CLI Tools roundup. Portability matters more than speed when your script has to run in CI on a minimal container image.

Will Ubuntu 26.04 break my scripts?

Almost certainly not. uutils aims for 100% GNU compatibility and currently hits roughly 88%. Most real-world scripts never touch the edge cases where uutils and GNU diverge. If you do hit a regression, you can install GNU coreutils from a fallback package. The project tracks known incompatibilities on GitHub.

Is there a benchmark I can run myself?

Yes. Install hyperfine and try this on any large codebase you have locally:

bash
hyperfine --warmup 3 \
  'grep -r "TODO" /path/to/repo' \
  'rg "TODO" /path/to/repo'

The warmup runs are critical — the first run often reads from cold cache and skews results. Always include at least 2–3 warmup iterations when benchmarking file-based CLIs.

What about ripgrep-all (rga)?

ripgrep-all wraps ripgrep to also search inside PDFs, ebooks, Office documents, and SQLite databases. It is a different product with a different performance profile — the bottleneck becomes the document parser, not the regex engine. Useful, but not the tool you reach for when searching plain source code.

Wrapping Up

The "Rust is fast" meme obscures the actual engineering story. Rust CLI tools are fast because their authors chose SIMD-accelerated algorithms, because the language makes parallel and zero-cost abstractions easy to write safely, and because they dared to break with POSIX tradition on defaults like .gitignore handling. A C rewrite with the same design decisions would be comparably fast — but nobody has written one, and Rust is where the energy is.

For everyday development, the practical takeaway is simple: use ripgrep, fd, and bat for interactive work, keep grep and find in your shell scripts, and watch the uutils project over the next few years as it moves toward full GNU compatibility. By the time Ubuntu 28.04 ships, the line between "the classic coreutils" and "the Rust coreutils" will probably have blurred to the point where nobody notices which one is installed.

If you want to actually install and configure these tools, the companion Modern Rust CLI Tools guide covers installation, aliases, and integration with fzf and your shell. For the classic grep to ripgrep migration path specifically, the grep and ripgrep deep dive walks through every flag you need. And if you are still figuring out which modern CLI tool solves which problem, the CLI tools map is the best place to start.

The short version: Rust did not break the laws of physics. It just made it cheap to write the code we should have been writing all along.