How to specify crate versions in a toml file

In a toml file, is there a good reason to write say

tokio = { version = "1.13.0", features = ["macros","rt-multi-thread","parking_lot"] }

rather than

tokio = { version = "1", features = ["macros","rt-multi-thread","parking_lot"] }

? If either is ok, which do you find preferable?

The specified version is supposed to be the oldest version of the library that your code works with. For example, if you have a project and you upgrade one dependency, then the dependency may have increased some of its minimum versions, which will cause cargo to also update those dependencies if the one in the Cargo.lock is now too old.

It probably doesn't matter much in applications, but it is important for libraries.

Have you seen this older topic? PSA: Please specify precise dependency versions in Cargo.toml

As alice said, it mostly makes a difference for libraries, not "final" binaries.

No, or if I did, I have forgotten all about it. I am struggling a bit with understanding the difference.

In my mind, as soon as someone runs "cargo update" they are going to get the latest (semver--compatible) version of a crate. So I don't yet understand why it matters. ( I am fairly sure I am wrong, but haven't really yet understood why. I am still confused. ).

[ Edit: but anyway, I have (by some miracle, without understanding why) up to this point being doing it right, and I will certainly continue! ]

As you can see in that topic, there's some controversy around it. But I'll try to put down my understanding...

When compiling a complex dependency graph, several crates might depend on the same library foo but specify different version in their Cargo.toml. If possible, cargo will merge these different requirements based on semver compatibility. In the end some version older than the latest version might be chosen.

If a crate bar specifies foo = "1", it effectively says foo = "1.0.0", even if the actual version used at the time of developing bar was foo = "1.2.3". So cargo is allowed to go down all the way to 1.0.0 when resolving dependencies. One of kornel's points was (I think) that that's probably not what the crate author meant: if they started developing bar when foo = "1.2.3", they're likely relying on features added to foo at the minor release 1.2.0.

A key point here is that according to semver upgrading from 1.0.0 to 1.2.3 is fine (only minor releases), but downgrading from 1.2.3 to 1.0.0 is not! Downgrading from 1.2.3 to 1.2.0 should be fine (only patch releases).

3 Likes

Ok, but I don't understand how that could happen. Can anyone explain that?

Three ways:

  • if there's an older version already in the lockfile, Cargo will keep the older version.
  • if any other crate explicitly limits version range with < operator
  • if user uses -Z minimal-versions option, which is handy for making projects compatible with older Rust versions (e.g. serde 1.0.143 requires Rust 1.51, serde 1.0.142 works with Rust 1.13).
4 Likes

Ok, now I understand. It does seem like these are to some extent "exceptional" situations, but finally I get it. Thanks!