Cargo refuses to compile multiple SemVer-compatible versions of the same crate

I'm rewriting the crate cargo-prefetch (https://crates.io/crates/cargo-prefetch) which allows to download the top crates before you go offline.

Like the original cargo prefetch, my implementation relies on calling cargo fetch. While testing my implementation which specifies the two dependencies semver-parser = "0.10.1" and cargo_metadata = "0.12.1", I stumbled on this conflict:

failed to select a version for `semver-parser`.
    ... required by package `cargo_metadata v0.12.1`
    ... which is depended on by `tmp v0.0.0`
versions that meet the requirements `=0.10.0` are: 0.10.0

all possible versions conflict with previously selected packages.

  previously selected package `semver-parser v0.10.1`
    ... which is depended on by `tmp v0.0.0`

failed to select a version for `semver-parser` which could resolve this conflict

This has been asked on github before, but discarded: Cargo is unable to resolve dependency versions · Issue #6584 · rust-lang/cargo · GitHub

I still fail to see how this is not a bug. It simply doesn't allow me to use a version of a crate if one of my other dependencies depends on a different, SemVer-compatible version of the same crate but that version and my version don't overlap (e.g. versions <=1.0.1 and =1.0.2 are SemVer-compatible but don't overlap).

Effectively, cargo is unwilling to compile two versions of the same crate.

Are there workarounds for this? Any plans to change this behavior?

Why would it? Cargo only includes two versions of the same crate if they're not semver compatible. If someone is using =0.10.0, that's on them. Rust very much prefers ^ requirements (to the point it is implicit). If you're manually restricting ranges, you shouldn't be surprised when you run into issues like this.

With regard to change behavior, this is from the issue you linked:

For changing this, yes it would require an RFC

It's generally a bad practice especially for published libraries to use version ranges like "=0.10.0".

It looks like cargo_metadata added this requirement in order to test with old Rust toolchains in CI. However, there are better ways to do this:

  • Include a Cargo.lock file in the repo for use in CI (at least while testing old toolchains).
  • Or, use cargo +nightly -Z minimal-versions to generate such a Cargo.lock file on demand.
2 Likes

In the pull request linked above, the cargo_metadata maintainers say this is is temporary until a new semver-parser version is released with this fix. For now, it looks like you can work around this by pinning your own crate to an older version of cargo_metadata. First, edit your Cargo.toml to allow cargo_metadata = "0.12.0", and then run this command to downgrade the version in Cargo.lock`:

cargo update -p cargo_metadata --precise 0.12.0

As for the reasons Cargo won't build multiple "compatible" versions of the same library: I believe one reason is that libraries might manage some global state or initialiazation that should exist only once per program. Currently, such libraries may need to take special care to avoid conflicts when they release a new major version. If arbitrary versions could be combined in a single program, then they would need to take the same special care with every single release.

Aside from that special case, linking to multiple semver-compatible versions is often the wrong thing to do, leading to type errors between libraries that are supposed to be able to interoperate.