Test only what changed. Keep dependencies unified. Split crates cleanly. Release in order.
Install • Why • Quick Start • Workflows • Results • GitHub Action
cargo-rail is a single tool that replaces a fragmented ecosystem:
| Problem | Before | After |
|---|---|---|
| Build graph drift | cargo-hakari, workspace-hack crates |
cargo rail unify |
| Unused deps | cargo-udeps, cargo-machete, cargo-shear |
cargo rail unify |
| MSRV guessing | cargo-msrv, compile-and-fail loops |
cargo rail unify |
| CI waste | paths-filter + 1k LoC shell |
cargo rail test |
| Crate extraction | git subtree, Copybara (Java) |
cargo rail split |
| Release chaos | release-plz (hundreds of deps), git-cliff |
cargo rail release |
11 core dependencies. 55 resolved. One config file.
cargo install cargo-railPre-built binaries: Releases • cargo binstall cargo-rail
Drop into any Rust workspace:
cargo install cargo-rail && cargo rail init
cargo rail unify --check # See what would change (read-only)That's it. See what cargo-rail would unify — no changes made until you run cargo rail unify.
demo.mp4
cargo rail unify on ripgrep — 9 deps unified, 6 dead features pruned
Most Rust monorepo tools solve one problem and introduce their own complexity. You end up with:
- 5+ config files across different tools
- Each tool parses
cargo metadatadifferently - No single source of truth for your dependency graph
- Shell scripts gluing everything together
cargo-rail takes a different approach:
- One tool, one config —
rail.tomldescribes everything - Resolution-based — Uses what Cargo actually resolved, not syntax parsing
- Multi-target aware — Runs
cargo metadata --filter-platformper target in parallel - Minimal footprint — 11 core deps vs 200-600+ in competing toolchains
Graph-aware. Only test what's affected by your changes:
cargo rail affected # Show affected crates
cargo rail affected -f names-only # Just names (for scripting)
cargo rail test # Run tests for affected crates only
cargo rail test --all # Override: test everythingFor CI: Use cargo-rail-action:
- uses: loadingalias/cargo-rail-action@latest
id: rail
with:
since: ${{ github.event.before }}
- run: cargo nextest run -p ${{ steps.rail.outputs.crates }}
if: steps.rail.outputs.docs-only != 'true'Keep your workspace lean. Per target triple:
cargo rail unify --check # Preview (CI-safe, exits 1 if changes needed)
cargo rail unify # Apply changes
cargo rail unify sync # Re-detect targets after adding new platforms
cargo rail unify undo # Restore from backupWhat it does:
- Unifies versions based on Cargo's resolver output
- Computes MSRV from the dependency graph
- Prunes dead features that are never enabled
- Detects unused deps (opt-in)
- Pins transitives — replaces
cargo-hakariwithout a workspace-hack crate
Extract crates with full git history. Keep them in sync:
cargo rail split init my-crate # Configure
cargo rail split run my-crate # Extract with history
cargo rail sync my-crate # Bidirectional sync
cargo rail sync my-crate --to-remote # Push to split repo
cargo rail sync my-crate --from-remote # Pull (creates PR branch)Three modes: single crate → new repo, multiple crates → new repo, or multiple crates → new workspace.
Dependency-order publishing with changelogs:
cargo rail release init my_crate # Configure
cargo rail release check my_crate # Validate
cargo rail release run my_crate --bump minor # ExecuteTested on production Rust workspaces:
| Repo | Members | Deps Unified | Dead Features | Notes |
|---|---|---|---|---|
| tikv | 72 | 61 | 3 | Largest stress test |
| meilisearch | 19 | 46 | 1 | Significant unification |
| helix-db | 6 | 18 | 0 | Growing project |
| helix | 12 | 16 | 1 | Editor workspace |
| tokio | 10 | 10 | 0 | Core ecosystem |
| ripgrep | 10 | 9 | 6 | CLI baseline |
| polars | 33 | 2 | 9 | Already clean |
| ruff | 43 | 0 | 0 | Already unified |
| codex | 49 | 0 | 0 | Already unified |
Each link above points to a fork with cargo-rail configured. Clone and compare.
Demo videos: examples/
5 minutes:
git checkout -b migrate-to-rail
rm -rf crates/workspace-hack # Remove the hack crate
cargo rail init
# Edit rail.toml: set pin_transitives = true
cargo rail unify
cargo check --workspaceFull guide: docs/migrate-hakari.md
Multi-Target Resolution — Most tools run cargo metadata once. cargo-rail runs it per target in parallel, computes feature intersections (not unions). If something is marked unused, it's unused across all your targets.
System Git — Uses your git binary directly. No libgit2, no gitoxide. Real git, real history, deterministic SHAs.
Lossless TOML — Uses toml_edit to preserve comments and formatting. Your manifests stay readable.
Supply-Chain Safety — 11 core dependencies. I built the release workflow specifically because I was uncomfortable with hundreds of deps for release automation.
How is this different from cargo-hakari?
cargo-hakari creates a workspace-hack crate. cargo-rail writes unified versions directly to [workspace.dependencies] — no extra crate, no hakari generate step. Enable pin_transitives = true for equivalent behavior. See the migration guide.
Does it work with workspace inheritance?
Yes. cargo-rail writes to [workspace.dependencies] and converts member manifests to { workspace = true }.
What about virtual workspaces?
Supported. For pin_transitives, cargo-rail auto-selects a workspace member as the transitive host (or set transitive_host explicitly).
Private registries?
Works via cargo metadata, which respects .cargo/config.toml. Private registry deps are pinned normally.
Issues, PRs, and feedback welcome. This is built for the Rust community.
Found this useful? Star the repo to help other Rust teams find it.
GitHub • Crates.io • GitHub Action
Built by @loadingalias