Cargo seems to ignore version of dependency

Continuing the discussion from Compiler version support breakage in semver:

Hi all, I have a little project for generating lorem ipsum text called lipsum and ran into a variation of the problem in the parent thread.

My project depends on the rand crate and back when I started out, I found that I could compile my code all the way back to rustc version 1.8.0 with rand version 0.3.0.

However, when I try to build the project today with Cargo from Rust 1.8.0, I see it downloading and compiling rand 0.4.2 -- which uses the ? operator and thus requires Rust 1.13 or later.

I've tried removing local caches, but to no avail:

% rm -rf ~/.cargo/{git,registry}
% rm Cargo.lock
% rustup default 1.8.0
info: using existing install for '1.8.0-x86_64-unknown-linux-gnu'
info: default toolchain set to '1.8.0-x86_64-unknown-linux-gnu'

  1.8.0-x86_64-unknown-linux-gnu unchanged - rustc 1.8.0 (db2939409 2016-04-11)

% cargo build
unused manifest key: badges.appveyor.repository
unused manifest key: badges.travis-ci.repository
unused manifest key: package.categories
    Updating registry ``
 Downloading rand v0.3.22
 Downloading rand v0.4.2
 Downloading libc v0.2.40
   Compiling rand v0.4.2
/home/mg/.cargo/registry/src/ 136:38 error: expected one of `.`, `;`, `}`, or an operator, found `?`
/home/mg/.cargo/registry/src/             rounds = ec.test_timer()?;

My Cargo.toml file has

rand = "0.3"

so I don't understand why Cargo would go and download the more recent version of rand?

Is this a bug in the old version of Cargo used here or do the version specifiers here work very differently from what I expect? My understanding is that a version number like 0.1.2 is shorthand for ^0.1.2, which in turn means "use a version that satisfies >=0.1.2 and <0.2.0", according to the Cargo manual.

To quote the readme from rand 0.3.22:

Version 0.3 has been replaced by a compatibility wrapper around rand 0.4. It is recommended to update to 0.4.

Basically, in order to avoid splitting the ecosystem around incompatible versions of rand due to old code depending on rand 0.3, rand 0.3.22 depends on rand 0.4, thus breaking said old code so it can't compile, thus cleverly avoiding the problem entirely.

...wait, that doesn't seem right... ( :stuck_out_tongue: )

Your only recourse is likely to manually restrict your dependency on rand to something like "<0.3.22".


Oh, thanks! It didn't occur to me at all that this could be done by the rand crate itself :smiley:

I guess I'll just update to version 0.4 and bump the minimum Rust version accordingly. Trying to keep code compatible with old versions of Rust seems to be a losing battle...

Just to clarify, to make sure that code always compiles, one has to use lockfile. If you don't have a lockfile, than the builds are not guaranteed to be reproducible and might break in the future for various reasons.

1 Like

Yes, I'm slowly learning this the hard way :smiley: However, the recommendation has been to commit a lock file for applications, not for libraries. Are we moving away from this recommendation?

Even if you commit a lock file for a library, Cargo will ignore it when using the library as a dependency.

The only thing that will really solve this problem is the day that Cargo lets you specify the version(s) of Rust a crate is compatible with. Until then, it's half-measures and much wailing and gnashing of teeth.


The end of your first post actually has the answer. Since you only specify parts 1 & 2 (and not 3) of the version cargo is free to increase parts parts 2 & 3. That is to say 0.3 is ^0.3 is >=0.3.0, <1.0.0. If you want to make sure it stays within the 0.3 series, you need to add another digit (0.3.0) or use an inequality requirement directly.

I'm bad a reading, ignore me.

That's not what's happening. Because the first digit is 0, the second digit is the effective major version. If you look at the output from Cargo, you'll see it is using a 0.3.x crate; it's just that that crate in turn depends on a 0.4.x crate.

1 Like

Heh, yeah, just saw that. My bad.

Thanks a lot for the help, you guys are great as always! :slight_smile:

If you want reproducible builds, you might want to commit a lockfile. One typically wants reproducible builds for binaries, but not for libraries (for libraries, "reproducible build" does not make sense, because each downstream dependency would have it's own slightly different build).

I think the following CI setup might be reasonable for libraries:

  • builds with stable rust, without lockfile, scheduled nightly. It might break if deps do bad releases. If that happens, you bug the dependency author to yank the bad version.

  • if you care about supporting really old version of rust, builds with that version, with lockfile or, when it is stable, with --minimal-versions flag. If the author of the application, which uses your library, also cares about old version of rust, they can use (manually) your lockfile to issue cargo update -p calls to lock your depnedencies to minor versions, which are compatbile with the target version of Rust.

What I hope for is simply that I can keep building my library with a given set of dependencies. Should be simple with a language and a package manager where semantic version is used throughout the ecosystem :smiley:

However, my impression is that everybody is eagerly pushing towards using new compiler features and when people do not bump their major versions while doing so, well then we end up with random breakage when you recompile code without having touched the dependencies.

It feels like the worst of both worlds: people say they use semantic versioning, and yet I see compilation failures again and again. To me, it would be better if there were no semantics in the version numbers in that case.

1 Like

Unfortunately, bumping semver when updating rustc requirement is also a wrong solution here: it will just lead to needless incompatibilities in the ecosystem, when you can't plug foo 8.0.0 into a function which expects foo 7.0.0, just because those foos need different rustc versions, but are otherwise compatible and don't need to be duplicated at all.

The correct solution, as @DanielKeep suggested, is to state rustc requirement in Cargo.toml and use it during dependency resolution, but we don't have that solution implemented yet, so, it seems to me, the best practical solution atm is for library authors to use --minimal-version/Cargo.lock, and for application developers to manually do Cargo's job and pick minor versions, compatible with their target rustc, themselves :slight_smile:

Anyway, I feel like I've basically repeating what I've said on the guidelines issue, so ccing that comment here :slight_smile: