I agree with the problem, but not the solution. You're absolutely right that crates specifying a version range (which is anything except =major.minor.patch
, which is rarely used) should make sure that their project is actually compatible with the range that they've declared.
I know people use vague versions with good intentions to give flexibility to downstream users, but this backfires! Your crate may get a 5-year-old version of a dependency you've never tested. If you want to be maximally flexible with the versions you specify, please actually test them!
Absolutely! This seems like the right solution to the problem; I did not know about minimal-versions
until now, but I'll definitely add a step to my CI pipelines that use this to verify that my version ranges are correct.
Vague versions also create a second, counter-intuitive problem. You may be specifying very old dependency versions thinking of users of old Rust versions that can't upgrade their dependencies. The problem is that keeping less-than-latest dependencies in Cargo is hard. Without an existing carefully-preserved antique lockfile, it's a game of whack-a-mole of locking down versions of a dependency of a dependency. People who had to do it rightfully hate it. It would be sooo much easier with the -Z minimal-versions
option, but ironically, the imprecise versions break this feature! Vague versions are not helping old Rust users: they're breaking the best tool they could have.
I don't even understand what is being said here. What's the second problem? That pinning old versions in an application is difficult? A dependency using a general version range doesn't cause this problem, its just a general Cargo problem. The only difference is that users don't even have the option of doing this if they really want to, if everyone uses too-specific of version ranges. I understand that this is a problem; I don't understand how this a problem with vague versions.
There's also a problem here not being mentioned should you choose to swing the other way of using precise dependency versions: API compatibility. When Cargo resolves a project's dependencies, it will do so in one of two methods:
- For each crate, try to find a crate version that meets the version ranges of all crates in the project.
- If the combined version ranges is not solveable with a single crate, attempt to solve it with multiple versions.
Now this is actually a feature (I talk about it in more depth here) but this can actually cause problems if a particular dependency being solved is actually part of an API being shared between crates, and this actually can come into play for projects that maintain an MSRV.
Let's say crate A uses a crate X as part of its public API, and crate B also uses crate X as part of its API, and application C uses both A and B together. Crate A seeks to maintain a strict MSRV, and thus doesn't want versions 1.4 or newer of crate X, but it is compatible with any other 1.x version, so it declares a version range on crate X as >=1.0.0, <1.4.0
. However, version 1.4 was recently released, so crate B sets its version as precisely as possible on X, so it sets its version as ^1.4.2
. This will now cause interop between crate A and B using types from X to fail compilation, because the only way that Cargo can solve the dependency tree for crate C is to select multiple X versions, and types from different crate versions are considered to be different types by the compiler. This could have been avoided if B was actually compatible with anything newer than, say, 1.1, and they set a more general version of ^1.1
.
So in summary: Yes, it is a problem when crates aren't actually compatible with dependency versions that they claim to be, but the solution isn't to simply narrow the version range, but rather to verify that your ranges are correct, and potentially narrow or widen them with what is actually compatible. If you want to be especially generous, if it is trivial to broaden the versions your project supports with just a simple change in your code, then perhaps you should do so.