rustrust-cargo

What does a caret version constraint mean in Rust Cargo?


I'm implementing Rust Cargo version requirements. In general, I have trouble understanding caret requirements as specified. I found What's the difference between tilde(~) and caret(^) in package.json?, but this question is about npm version requirements of which I'm not sure if it is the same as Rust Cargo version requirements.

At https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html#caret-requirements, I read:

Caret requirements allow SemVer compatible updates to a specified version. An update is allowed if the new version number does not modify the left-most non-zero digit in the major, minor, patch grouping. In this case, if we ran cargo update -p time, cargo should update us to version 0.1.13 if it is the latest 0.1.z release, but would not update us to 0.2.0. If instead we had specified the version string as ^1.0, cargo should update to 1.1 if it is the latest 1.y release, but not 2.0. The version 0.0.x is not considered compatible with any other version.

Here are some more examples of caret requirements and the versions that would be allowed with them:

^1.2.3  :=  >=1.2.3, <2.0.0
^1.2    :=  >=1.2.0, <2.0.0
^1      :=  >=1.0.0, <2.0.0
^0.2.3  :=  >=0.2.3, <0.3.0
^0.2    :=  >=0.2.0, <0.3.0
^0.0.3  :=  >=0.0.3, <0.0.4
^0.0    :=  >=0.0.0, <0.1.0
^0      :=  >=0.0.0, <1.0.0

This compatibility convention is different from SemVer in the way it treats versions before 1.0.0. While SemVer says there is no compatibility before 1.0.0, Cargo considers 0.x.y to be compatible with 0.x.z, where y ≥ z and x > 0.

I'm confused about

In this case, if we ran cargo update -p time, cargo should update us to version 0.1.13 if it is the latest 0.1.z release, but would not update us to 0.2.0.

With what version requirement is this the case? It seems left out of the sentence. It continues to refer to a seemingly missing caret version requirement:

If instead we had specified the version string as ^1.0, cargo should update to 1.1 if it is the latest 1.y release, but not 2.0.

In which it refers to >1.0 as the version string (which I miss in the sentence before).

If I dissect the examples, my reasoning is as follows:

^1.2.3  :=  >=1.2.3, <2.0.0 // same as >=1.2.3 AND 1.*
^1.2    :=  >=1.2.0, <2.0.0 // same as >=1.2 AND 1.*, which condenses into 1.2.*
^1      :=  >=1.0.0, <2.0.0 // same as 1.*
^0.2.3  :=  >=0.2.3, <0.3.0 // same as >=0.2.3 AND 0.2.*
^0.2    :=  >=0.2.0, <0.3.0 // same as 0.2.*, which condenses into 0.2.*
^0.0.3  :=  >=0.0.3, <0.0.4 // huh
^0.0    :=  >=0.0.0, <0.1.0 // same as >=0.0.0 AND 0.0.*
^0      :=  >=0.0.0, <1.0.0 // same as >=0.0.0 AND 0.*, which condenses into 0.*

So except for my understanding of the 6th example (^0.0.3), my conclusion is that caret version requirements are exactly as wildcard version requirements, except when PATCH is specified in which the wildcard version requirement ANDs with the >= {version} (equal or later then) comparison version requirement.

Is this understanding correct and why is example 6 as it is?


Solution

  • Unlike in npm, the default version requirement range is indeed the caret requirement! This is stated in the Cargo reference on "specifying dependencies", just before the section linked in the question.

    The string "0.1.12" is a semver version requirement. Since this string does not have any operators in it, it is interpreted the same way as if we had specified "^0.1.12", which is called a caret requirement.

    As such, the following two dependency specifications are equivalent.

    time = "0.1.12"
    
    time = "^0.1.12"
    

    This, by the way, is the requirement referred to in the rest of the document. An update of time could bring in a version higher than 0.1.12, but never 0.2.0 or higher.

    See also: