brew-rs was an experiment in replacing parts of Homebrew's Ruby frontend with
Rust. It aimed to make common Homebrew commands significantly faster while
preserving Ruby-compatible output and behaviour.
The experiment was abandoned. Local benchmarks showed that Rust was faster for narrow, API-backed bottle fetch batches, especially when archive data was already cached. That did not translate into a better Homebrew install path. When installs included the full Homebrew work of pouring bottles, linking files, writing metadata, running health checks and preserving existing semantics, Ruby was faster on representative fully native comparisons.
The Rust frontend was also only meaningful when it avoided delegation entirely. Delegating back to Ruby added handoff cost and did not answer whether a fully Rust implementation could outperform a fully Ruby implementation. Once delegated paths were excluded, the strongest Rust result was bottle fetching, not installing.
This repository now serves as a record of the Rust frontend experiment and the benchmark work that ended it. The Rust code can still be useful for comparison, but it is no longer considered a shipping direction for Homebrew performance work.
The performance focus moved back to Ruby. The high-level goal became to improve
brew install by reducing the time before useful network or disk I/O begins,
using API-backed bottle metadata earlier and reducing overhead in simple bottle
fetch paths without duplicating Homebrew's install semantics in another
frontend.
Representative zero-delegation benchmarks in a staged local Homebrew prefix showed this split:
| Operation | Ruby | Rust | Result |
|---|---|---|---|
| Fetch 100 bottles, archive cold | 13.07s | 11.42s | Rust 1.14x faster |
| Fetch 100 bottles, archive warm | 1.513s | 0.379s | Rust 3.99x faster |
| Fetch 6 bottles, archive cold | 2.274s | 1.471s | Rust 1.55x faster |
| Fetch 6 bottles, archive warm | 0.842s | 0.313s | Rust 2.69x faster |
| Install 50 simple bottles, archive warm | 8.87s | 93.54s | Ruby 10.5x faster |
| Install 50 simple bottles, archive cold | 75.71s | 132.27s | Ruby 1.75x faster |
| Install 6 bottles, archive cold | 5.287s | 7.632s | Ruby 1.44x faster |
| Install 6 bottles, archive warm | 1.924s | 6.009s | Ruby 3.12x faster |
The useful conclusion was not that Rust could never beat Ruby at individual
operations. It could. The conclusion was that the operations where Rust won were
too narrow to justify a separate frontend, while the operations users most care
about for brew install remained faster in Ruby.
Warm-cache reinstall-style benchmarks were treated as synthetic. They mostly measured repeating work after bottles and metadata were already local, rather than the more common path where Homebrew resolved metadata, downloaded missing bottles, poured kegs, linked files, wrote tabs and handled postinstall work.
Benchmarks based on a separate content-addressed store were also not treated as
evidence that Homebrew could be made faster by swapping frontends. They measured
a different system: one that could skip Homebrew's Cellar layout, tabs, links,
caveats, postinstall semantics, cask behaviour, source fallback, tap logic and
auditability. That can make a demo look quick, but it does not show a faster
brew install unless it preserves the compatibility work users expect from
Homebrew.
The comparison that mattered was fully Ruby versus fully Rust, zero delegation, with cold archive I/O included.
The Rust work informed several ideas for improving the Ruby frontend directly:
-
Start useful I/O earlier in
brew install.- Focus on the time between process start and the first bottle manifest or bottle archive transfer, not only total command time.
- Use
brew prof install FORMULAto separate startup, require, formula resolution and download enqueue costs. - Compare with
hyperfine --warmup 0in archive-cold and archive-warm modes, removing the targetbrew --cache --formula FORMULApaths before cold runs. - Treat
Library/Homebrew/cmd/install.rb,formula_installer.rb,fetch.rbanddownload_queue.rbas the main flow to shorten before touching pour/link correctness.
-
Use API-backed bottle metadata before full
Formulainflation where possible.- Investigate
FormulaStructuse for simplehomebrew/corebottles so Ruby can decide bottle URLs, tags and SHA256 values without building every full formula object up front. - Keep full
Formulaobjects for dependency resolution, requirements, postinstall, caveats, tabs and non-trivial formula logic. - Compare
brew fetch FORMULA...and the fetch phase ofbrew install FORMULA...for 1, 10, 50 and 100 formula batches. - Check whether
Formulary.factory,args.named.to_formulae_and_casksand repeated bottle tag simulation dominate simple bottle fetches.
- Investigate
-
Make immutable bottle cache checks cheaper.
- GHCR bottle blobs with API-provided SHA256 metadata should not need the same freshness path as arbitrary URLs, mirrors, casks, source tarballs and VCS strategies.
- Profile
CurlDownloadStrategy#resolve_url_basename_time_file_size,Utils::Curl.curl_headers,AbstractFileDownloadStrategy#cached_locationand checksum verification. - Measure the cost of
Pathname.globcache discovery and curl header probes on warm-cachebrew fetchruns. - Preserve current generic
download_strategybehaviour for non-bottle URLs, source fallback, mirrors, content-disposition filenames, partial downloads and cask-specific handling.
-
Use
brew updateto improve Ruby startup and load performance.- Prewarm stable Bootsnap caches for the
brew installandbrew fetchload graph after update work that already changes Homebrew's Ruby files. - Avoid cache keys that churn on every checkout path, timestamp or vendor state change.
- Compare the first command after
brew updatewith and without warmed Bootsnap caches usinghyperfine --warmup 0. - Use
brew prof --stackprof install FORMULAto check whether require/load cost actually moved, rather than only hiding it behind a warm shell.
- Prewarm stable Bootsnap caches for the
-
Add first-class phase timing around real install work.
- Track planning, API metadata load, full formula inflation, dependency resolution, download enqueue, curl headers, curl body, checksum, symlink, extraction, pour, link, tab writing, cleanup and postinstall.
- Keep timings opt-in and machine-readable enough to compare repeated runs.
- Use the phase data to interpret
brew profoutput instead of relying on wall-time tables alone. - Make cold-cache tests explicit: metadata cold, archive cold, archive warm and fully warm are different workloads.