Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Why Is It Designed This Way?

The most reusable lessons appear when we compare ripgrep’s choices with plausible alternatives.

Why not one producer and one shared work channel?

A central queue is simpler to sketch, but it naturally produces breadth-first behavior and concentrates contention. Ripgrep wants local depth-first traversal because wide trees can retain enormous numbers of paths and inherited ignore matchers. LIFO local deques preserve locality; stealing repairs imbalance.

Why not one shared SearchWorker behind a mutex?

Searching mutates scratch buffers and printer state. A mutex around one worker would serialize the expensive operation and erase useful parallelism. Cloning a worker per thread duplicates bounded scratch state while removing the hottest lock.

Why not write each matching line directly to stdout?

Line-sized locking permits output from multiple files to interleave and pays a synchronization cost for every line. Holding the lock for the entire search of a file prevents tearing but also holds it during file reads and matching. Private file buffers move the lock to the short commit phase.

Why not preserve sorted output with a result collector?

That is possible, but the collector must retain results for later files while waiting for earlier files to finish. A slow early file can make memory grow with all later output. Ripgrep chooses bounded, immediate output in parallel mode and stable order in sequential mode.

Why custom work stealing instead of Rayon?

Rayon is well suited to recursively divisible CPU work. Ripgrep’s traversal has domain-specific requirements: inherited ignore state, depth-first local order, visitor construction and cleanup per thread, cooperative Skip/Quit, and termination when dynamically generated directory work is exhausted.

The lesson is not that application-specific schedulers are generally better. It is that a concurrency abstraction should preserve the application’s unit of work and shutdown semantics. Here those semantics live inside ignore, which is itself reusable by applications other than ripgrep.

Why no async runtime?

An async rewrite would add future state, executor integration, and async-aware filesystem decisions without changing the basic need for CPU parallelism. The current worker count already bounds simultaneous blocking reads and searches. For this workload, threads make blocking and computation part of the same simple execution path.