Upgrade Flexibility vs. Forced Synchronicity

Cargo allows many versions of a single crate to be used across a larger codebase without conflict.

As a Rust developer, this aspect of Cargo is so pervasive and painless it's easy to forget that not all package managers and/or language import semantics allow this. There are many languages, including major ones with many more users than Rust, which do not permit this paradigm. In other cases it is impossible in practice.

(Not only can multiple versions of the same crate be used, but code utilizing competing Rust editions can be used entierely interchangeably, which is amazing for anyone who endured certain difficult transitions between language versions.)

To demonstrate the idea of multiple version coexistence, consider the following example:

# Cargo.toml

[package]
name = "my-crate"

[dependencies]
reqwest = "0.11" # <- depends on base64 v0.13
base64 = "0.12"

The reqwest crate also depends on base64, but on a newer version, 0.13. But this poses no problem for Cargo, the two versions of base64 can coexist peacefully in the larger codebase.

This is so easy and common that you probably no longer give it a second thought.

However, things become more complicated when you step away from the semantic versioning-based dependency resolution of registry dependencies.

Consider this alternative scenario, using git-based dependencies:

[package]
name = "a"

[dependencies.b]
git = "https://my-git-server.com/proprietary-crate-b.git"
branch = "master"

[dependencies.c]
git = "https://my-git-server.com/proprietary-crate-c.git"
branch = "master"

And in the Cargo.toml for b:

[package]
name = "b"

[dependencies.c]
git = "https://my-git-server.com/proprietary-crate-c.git"
branch = "master"

This creates a problem which was not present for the registry-based dependencies above (reqwest using base64 version "0.13" while my-crate depends on base64 version "0.12").

Namely, it's not possible for b and c to depend on different versions of c, as configured. They both depend on "master". As far as has been specified to Cargo, they depend on the same thing. But if one of the crates (say, b) hadn't run cargo update in a while (perhaps to avoid the upgrade conflicts that arise from git-based dependencies, it might, in practice, have been depending on a much different point in "master" as compared to c.


  "b" was depending on "this" master
   |
   |
x--x--x--x--x--x--x--x--x-> master      
   ^           |
   |           |
   ----->     "c" was depending on "this" master
   |
   code at the b, c
   commits is not compatible


   ------ time ----->

Thus, when you attempt to build these three crates together, it won't actually build, and the reasons why are complex and confusing. Meanwhile, the headache could have been avoided entirely by utilizing a registry server and the semantic versioning-based dependency resolution model that is the norm for open source crates.