| |
| ▲ | 0xffff2 a day ago | parent | next [-] | | So there's no difference at all between "0", "0.9" and "0.9.3" in cargo.toml (Since semver says only major version numbers are breaking)? As a decently experienced Rust developer, that's deeply surprising to me. What if devs don't do a good job of versioning and there is a real incompatibility between 0.9.3 and 0.9.4? Surely there's some way to actually require an exact version? | | |
| ▲ | Diggsey 18 hours ago | parent | next [-] | | They are different: "0": ">=0.0.0, <1.0.0"
"0.9": ">=0.9.0, <1.0.0"
"0.9.3": ">=0.9.3, <1.0.0"
Notice how the the minimum bound changes while the upper bound is the same for all of them.The reason for this is that unless otherwise specified, the ^ operator is used, so "0.9" is actually "^0.9", which then gets translated into the kind of range specifier I showed above. There are other operators you can use, these are the common ones: (default) ^ Semver compatible, as described above
>= Inclusive lower bound only
< Exclusive upper bound only
= Exact bound
Note that while an exact bound will force that exact version to be used, it still doesn't allow two semver compatible versions of a crate to exist together. For example. If cargo can't find a single version that satisfies all constraints, it will just error.For this reason, if you are writing a library, you should in almost all cases stick to regular semver-compatible dependency specifications. For binaries, it is more common to want exact control over versions and you don't have downstream consumers for whom your exact constraints would be a nightmare. | |
| ▲ | a day ago | parent | prev | next [-] | | [deleted] | |
| ▲ | steveklabnik a day ago | parent | prev [-] | | Note that in the output, there is rand 0.9.0, and two instances of rand_core 0.9.3. You may have thought it selected two versions because you missed the _core there. > So there's no difference at all between "0", "0.9" and "0.9.3" in cargo.toml No, there is a difference, in particular, they all specify different minimum bounds. The trick is that these are using the ^ operator to match, which means that the version "0.9.3" will satisfy all of those constraints, and so Cargo will select 0.9.3 (the latest version at the time I write this comment) as the one version to satisfy all of them. Cargo will only select multiple versions when it's not compatible, that is, if you had something like "1.0.0" and "0.9.0". > Surely there's some way to actually require an exact version? Yes, you'd have to use `=`, like `=0.9.3`. This is heavily discouraged because it would lead to a proliferation of duplication in dependency versions, which aren't necessarily unless you are trying to avoid some sort of specific bugfix. This is sometimes done in applications, but basically should never be done in libraries. | | |
| ▲ | 0xffff2 a day ago | parent [-] | | Sorry, I don't understand the "^ operator" in this context. Do I understand correctly that cargo will basically select the latest release that matches within a major version, so if I have two crates that specify "0.8" and "0.7.1" as dependencies then the compiler will use "0.8.n" for both? And then if I add a new dependency that specifies "0.9.5", all three crates would use "0.9.5"? Assuming I have that right, I'm quite surprised that it works in practice. | | |
| ▲ | steveklabnik a day ago | parent [-] | | It’s all good. Let me break it down. Semver specifies versions. These are the x.y.z (plus other optional stuff) triples you see. Nothing should be complicated there. Tools that use semver to select versions also define syntax for defining which versions are acceptable. npm calls these “ranges”, cargo calls them “version requirements”, I forget what other tools call them. These are what you actually write in your Cargo.toml or equivalent. These are not defined by the semver specification, but instead, by the tools. They are mostly identical across tools, but not always. Anyway, they often use operators to define the ranges (that’s the name I’m going to use in this post because I think it makes the most sense.) So for example, ‘>3.0.0’ means “any version where x >= 3.” “=3.0.0” means “any version where x is 3, y is 0, and z is 0” which 99% of the time means only one version. When you write “0.9.3” in a Cargo.toml, you’re writing a range, not a version. When you do not specify an operator, Cargo treats that as if you use the ^ operator. So “0.9.3” is equivalent to “^0.9.3” what does ^ do? It means two things, one if x is 0 and one if x is nonzero. Since “^0.9.3” has x of zero, this range means “any version where x is 0, y is 9, and z is >= 3.” Likewise, “0.9” is equivalent to “^0.9.0” which is “any version where x is 0, y is 9, and z is >=0.” Putting these two together: 0.9.0 satisfies the latter, but not the former
0.9.1 satisfies the latter, but not the former
0.9.2 satisfies the latter, but not the former
0.9.3 satisfies both
Given that 0.9.3 is a version that has been released, if one package depends on “0.9” and another depends on “0.9.3”, version 0.9.3 satisfies both constraints, and so is selected.If we had “0.8” and “0.7.1”, no version could satisfy both simultaneously, as “y must be 8” and “y must be 7” would conflict. Cargo would give you both versions in this case, whichever y=8 and y=7 versions have the highest z at the time. | | |
| ▲ | 0xffff2 an hour ago | parent [-] | | Awesome. Thanks for taking the time. Glad to understand all of this better. I feel a bit silly now meticulously going through and changing all of my "0.9.3"s to "0.9" in the past, but at least now I know better. | | |
| ▲ | steveklabnik 34 minutes ago | parent [-] | | You're welcome! It is true that, if the change works on z < 3, you are expanding the possible set of versions a bit, so it's not useless; one could argue that you should only depend on z != 1 if there's a bug you want to make sure that you use the versions past when it works, otherwise, no reason to restrict yourself, but it's not a big deal either way :) |
|
|
|
|
| |
| ▲ | eximius a day ago | parent | prev [-] | | This doesn't sound right. If A depends on B and C - B and C can each bring their own versions of D, I thought? | | |
| ▲ | Diggsey 18 hours ago | parent | next [-] | | Within a crate graph, for any given major version of a crate (eg. D v1) only a single minor version can exist. So if B depends on D v1.x, and C depends on D v2.x, then two versions of D will exist. If B depends on Dv1.2 and C depends on Dv1.3, then only Dv1.3 will exist. I'm over-simplifying a few things here: 1. Semver has special treatment of 0.x versions. For these crates the minor version depends like the major version and the patch version behaves like the minor version. So technically you could have v0.1 and v0.2 of a crate in the same crate graph. 2. I'm assuming all dependencies are specified "the default way", ie. as just a number. When a dependency looks like "1.3", cargo actually treats this as "^1.3", ie. the version must be at least 1.3, but can be any semver compatible version (eg. 1.4). When you specify an exact dependency like "=1.3" instead, the rules above still apply (you still can't have 1.3 and 1.4 in the same crate graph) but cargo will error if no version can be found that satisfies all constraints, instead of just picking a version that's compatible with all dependents. | |
| ▲ | steveklabnik a day ago | parent | prev [-] | | can does not mean must. Cargo attempts to unify (aka deduplicate) dependencies where possible, and in this case, it can find a singular version that satisfies the entire thing. |
|
|