Advantages of Using a Private Cargo Registry

Cargo Dependencies: An Evolutionary Account

If you had to pick just one reason why Rust has been the most loved programming language in Stack Overflow's annual survey for six years running, you could do a lot worse than its beloved package manager, Cargo.

It's not just that adding dependencies is effortless, to the point that devs, oft-forgetting the days when code reuse meant hoarding a bloom filter, concurrent HashMap, and bespoke circular buffer in the "vendor" dir, wonder if Cargo might make it a little bit too easy, encouraging dreaded "micro-dependencies."

It's not just that the semantic versioning-based dependency resolution gives you fine-grained control over upgrading, with hardly any effort, such that cargo update is generally a worry-free experience.

It's not just that many versions of the same crate can coexist peacefully across the same codebase, unlike, say, Python, where One True Version is enforced for every package, and circular dependency hell often lurks around the bend.

(Speaking of which, It's not just that code utilizing competing Rust editions can be used entirely interchangeably, skipping effortlessly over chasms that caused decades of pain in other ecosystems.)

And it's not just that extremely nice, generated documentation comes for free just by writing (and commenting) the code, and the docs for every published crate version are automatically generated and published at Docs.rs, providing a hugely valuable information resource for anyone connected to the Internet.

No, it's all of those things together, the combination of which is a code reuse experience that may be unmatched by any other programming language.

And yet. For Rust developers working on larger, proprietary codebases, code reuse can often be a "Tale of Two Cities". Adding public, open-source dependencies from Crates.io is a breeze, of course. But private crates are relegated to a second-tier experience, suffering from many of the same problems that Cargo solves so wonderfully.

In the Beginning, There Were Path-Based Dependencies

Our story begins with path-based dependencies, introduced as a quickie solution when you're prototyping some new project:

# Cargo.toml

[dependencies.my-private-crate]
path = "../some-local-dir"

Instead of a range of semantic versions, from which Cargo will pull the best available candidate, we have instead instructed Cargo to use whatever happens to currently reside in ../some-local-dir.

For a solo dev, the crate in ../some-local-dir most likely already exists locally. But everyone else has to fetch the code manually, and ensure it's at the right path. That's not a huge deal when it's one crate. But at five crates? Ten? Hope you're good at shell scripting!

More fundamentally, path-based deps point to whatever is currently present at the specified path, which also create headaches like:

  • you check out a WIP branch in one crate's directory, now a second crate won't build
  • you forget which version of the code is in the other crate's directory, and waste time debugging the problem

This gets old quickly. There must be a better way ... and there is!

Git-Based Dependencies

# Cargo.toml

[dependencies.my-private-crate]
git = "ssh://my-repo.com/my-private-crate.git"
branch = "master"

Much better! Now Cargo can go fetch the code on its own if we don't have it yet, and even upgrade to the latest code via cargo update!

Except, very soon after pushing a breaking change to my-private-crate, you realize -- "master" isn't a version. Like path-based dependencies, a git branch is a moving target.

Soon, you find yourself avoiding cargo update, which has morphed from a relatively pain-free experience into a possible headache with a highly uncertain outcome.

To prevent breakage, you might check-in Cargo.lock into version control even for library crates. But the overall fragility of the system causes delays to grow between when you release code improvements in one crate and when those actually get used by downstream users.

There must be a better way ... and there is!

Version Branches

# Cargo.toml

[dependencies.my-private-crate]
git = "ssh://my-repo.com/my-private-crate.git"
branch = "v0.1.x"

Version branches are a way to provide a coarse approximation of semver-based dependency resolution by keeping separate histories for each minor version on its own branch, e.g. v0.1.x and v0.2.x.

This allows progress to advance on the newest branch (v0.2.x ), while keeping a stable target in, and possibly backporting important fixes to, the previous version branches (v0.1.x).

Version branches avoid many of the incompatibility problems of git-based dependencies which specify only the primary branch.

For example, cargo update is no-longer open-ended, but bounded to the changes that are pushed to a specific version branch.

Also, if you have multiple crates that depend on a single internal crate, they can each use different versions (or version branches, at least) simultaneously.

Version branches aren't as precise as Cargo.toml version requirements, of course. For example, it's a pain to go back to a specific patch release, should you need to (at least without release tags or some other additional layer in the version control history to denote specific version releases).

A similar approach -- depending on git tags or specific refs -- suffers the inverse problem: the dependency is fixed forever, unless manually altered; cargo update no longer brings small improvements from patch and minor releases automatically.

But honestly, the biggest downside of using elaborate version control history to reinvent an approximation of Cargo's semantic versioning-based dependency resolution is that it requires a lot of tedious effort to maintain.

It's enough effort to prompt a nagging, pestering thought: is this actually more work than it would take to set up a proper registry server -- the built-in, full-fledged solution to these problems provided by the world-class package manager we're using?

Private Registry

# Cargo.toml

[dependencies.my-private-crate]
version = "0.1.0"
registry = "my-private-registry"

In our experience, the advantages of moving to a private registry feel like they should have been obvious beforehand, but weren't.

The small, annoying problems -- papered-over, endured, sometimes even forgotten over years -- suddenly cease. Adding and upgrading internal crates becomes just as seamless as using Crates.io crates.

At that point, it dawns on you. Yes, of course a registry works wonderfully.

It works ... just like ... Cargo does!

With a Shipyard.rs private registry, you also get automatic Rustdoc builds, with full version history, just like Docs.rs. We are the only service on the market that gives you docs!

A private registry provides a superior experience for reusing internal Rust code, empowering your team and increasing productivity.

So create a private registry for free, and find out what you've been missing today!