Literal grep in pure Zig
Ripgrep speed, one static binary, zero excuses.
Recursive search scans every byte on every query. ziggygrep cuts the cost per byte instead of indexing around it.
First byte prefilter skips what cannot match, line bounding runs on verified hits only. Output matches rg -F on all 7 test patterns.
Free MIT · 656 KB Windows · 326 KB Linux · 22/22 benches at or below rg
Why it earns a slot
Less work per byte, not work avoided.
No index to build, no daemon to feed. Each stage removes cost that regex pipelines pay on every file.
- Literal-first engineFirst byte prefilter plus
eqlverify. Positions that cannot start the pattern cost one memchr step, zero slicing. - Threaded file sweepFixed
fetchAdd(8)batches from one file list, per worker scratch buffers, files sorted at merge. Lists of 8 or fewer files run direct. - Fused scan plus formatHeaders built once on the stack, written once into the buffer. No span array, no second pass.
- Flat memoryStreaming reads, no mmap, 256 KB accum with one flush, 60 KB pipe-safe chunks. Works on pipes.
Measured bench
Every cell is ziggygrep ms vs rg ms.
200 file 53 MB Rust corpus, ReleaseFast x86_64-v3, stdout piped to null, median of 9. Baseline rg 15.2.0 --no-config -F --no-heading --line-number.
| Pattern | Full | -c | -l |
|---|---|---|---|
| he | 38 vs 44 | 28 vs 35 | 17 vs 21 |
| in | 85 vs 96 | 40 vs 55 | 17 vs 20 |
| HashMap | 37 vs 40 | 33 vs 43 | 16 vs 20 |
| pub fn | 66 vs 92 | 33 vs 54 | 15 vs 23 |
| x | 66 vs 105 | 33 vs 51 | 19 vs 20 |
| fn | 79 vs 110 | 38 vs 55 | 15 vs 20 |
| e | 87 vs 102 | 31 vs 48 | 15 vs 18 |
| miss | 24 vs 24 tie | - | - |
GNU grep spot check on the same box: miss 128.7ms vs 17.9ms, HashMap 46.5ms vs 25.8ms. Speedups are ratios of medians on that fixture; your disk and cache shape your numbers.
Internals
Three stages, zero wasted bytes.
Prefilter, then confirm
Vector first byte scan skips 32 bytes per step. Line bounding runs on verified hits only. Same line hits dedupe, finished lines jump past.
Batch steal, sorted merge
Atomic index hands out fixed batches. Each worker owns scratch, carry, and chunk buffers. Merge sorts by file for deterministic output.
Buffer, then one flush
Headers built on the stack, written once. Pipes cap transport at 60 KB chunks. No mmap, flat memory, pipe safe.
Quickstart
One clone, one build, first hit in a minute.
Needs Zig 0.16.0 only. No packages, no services, no accounts.
git clone https://github.com/AkashPriyadarshii/ziggygrep.git
cd ziggygrep
zig build -Doptimize=ReleaseFast
./zig-out/bin/ziggygrep HashMap .
- Search one pattern
Runziggygrep TODO src/main.zig. Matches print aspath:lineno:line. - Count or list
Runziggygrep -c HashMap .for counts,ziggygrep -l fn .for file names. - Pipe it
No paths means stdin. Exit0match,1no match,2error. Aliaszggif you type it daily.
Limits
What it refuses to be.
- No regexPatterns are literal bytes. Use ripgrep when you need regex.
- No .gitignore in v0.1Dotfiles and
.gitare skipped; full ignore support is planned. - No colors, context, JSONNot in v0.1. Pipe to
lesswhen you want paging. - No index, no daemonEvery query scans. That is the design.
Ecosystem