# Changing Cargo semver compatibility for pre-releases

**URL:** <https://internals.rust-lang.org/t/changing-cargo-semver-compatibility-for-pre-releases/14820>\
**Category:** language design\
**Created:** [June 3, 2021, 8:24am UTC](https://internals.rust-lang.org/t/changing-cargo-semver-compatibility-for-pre-releases/14820 "2021-06-03T08:24:52Z")\
**Posts on this page:** 16\
**Page:** 1

<div class="post-metadata">

**Author:** ![djc](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/djc/32/1592_2.png) [@djc](https://internals.rust-lang.org/u/djc)\
**Post date:** [June 3, 2021, 8:24am UTC](https://internals.rust-lang.org/t/changing-cargo-semver-compatibility-for-pre-releases/14820/1 "2021-06-03T08:24:52Z")

</div>

Currently, Cargo relies on the semver crate, which tries to adhere to the semver spec as much as possible. However, in the context of Rust's crate compatibility ecosystem, I personally believe the current behavior is surprising and can easily lead to broken builds where this is unexpected.

## Problem

> <https://github.com/dtolnay/semver/issues/172>
>
> This test fails:
> 
> \`\`\`diff
> diff --git a/tests/regression.rs b/tests/regression….rs
> index a47a777..041bd7c 100644
> \--- a/tests/regression.rs
> +++ b/tests/regression.rs
> @@ -28,3 +28,22 @@ fn test\_regressions() {
> }
> }
> }
> +
> +// See https://github.com/RustSec/cargo-audit/issues/17
> +#\[test\]
> +fn test\_suffix() {
> + let req = semver::VersionReq::parse("\>= 0.10.2").unwrap();
> + let version = semver::Version::parse("0.12.0-pre.0").unwrap();
> +
> + // Test parsing
> + assert\_eq!(version.major, 0);
> + assert\_eq!(version.minor, 12);
> + assert\_eq!(version.patch, 0);
> + assert\_eq!(version.pre, vec!\[
> + semver::Identifier::AlphaNumeric("pre".into()),
> + semver::Identifier::Numeric(0),
> + \]);
> +
> + // Test matching
> + assert!(req.matches(&version));
> +}
> \`\`\`
> 
> It matches the req if I remove the "-pre.0" suffix though.
> 
> This causes cargo-audit to \[report wrong vulnerabilities\](https://github.com/RustSec/cargo-audit/issues/17).
> 
> I assume this is a bug, and does not follow the spec, right?

> For the match expression `>= 2.0.0` , here's the output of `VersionReq::matches` for the following versions:
> 
> - `2.0.0-rc.0` : false
> - `2.0.0` : true
> - `3.0.0-rc.0` : false
> - `3.0.0` : true
> - `4.0.0-rc.0` : false
> - `4.0.0` : true

> <https://github.com/dtolnay/semver/issues/236>
>
> This is a somewhat analogous problem to #172, which got me looking at the SemVer… ranges spec.
> 
> https://github.com/semver/semver/blob/efcff2c838c9945f79bfd21c1df0073271bcc29c/ranges.md#semantic-version-range-specification-semver-ranges
> 
> \> SemVer Versions that include PRERELEASE identifiers are assumed to be unstable and/or incomplete.
> \>
> \> Additionally, the presence of PRERELEASE identifiers in a version such as I.J.K-PRERELEASE causes the SemVer Version \> to have a lower precedence than I.J.K. Thus, it could cause a Range such as \<2.0.0 to include the version 2.0.0-rc.0,
> \> which likely contains breaking API changes that the consumer is not prepared to handle.
> \>
> \> To avoid surprising behavior for consumers...
> 
> I feel like this sort of "surprising" behavior exists in the interpretation of these \`VersionReq\`s:
> 
> \- \`2.0.0-rc.0\`
> \- \`^2.0.0-rc.0\`
> 
> These are interpreted as: \`\>=2.0.0-rc.0, \<3.0.0\`
> 
> To me falls under the "surprising behavior for consumers" category. It means anyone who uses such a requirement will be automatically opted in to upgrading to a final 2.0.0 release, which is problematic if any breaking changes occur between \`2.0.0-rc.0\` and the final \`2.0.0\` release.
> 
> It's possible to avoid this by (as the end user) writing this requirement explicitly as \`=2.0.0-rc.0\`, which is what I always end up doing to avoid the aforementioned breakages. But per my reading of prerelease handling in the ranges spec, prereleases are supposed to be disjoint from release versions specifically to address that problem.
> 
> It seems like these prerelease \`VersionReq\`s should expand to something much more restrictive, such as \`\>=2.0.0-rc.0, \<2.0.0\`.

> I feel like this sort of "surprising" behavior exists in the interpretation of these `VersionReq` s:
> 
> - `2.0.0-rc.0`
> - `^2.0.0-rc.0`
> 
> These are interpreted as: `>=2.0.0-rc.0, <3.0.0`
> 
> To me falls under the "surprising behavior for consumers" category. It means anyone who uses such a requirement will be automatically opted in to upgrading to a final 2.0.0 release, which is problematic if any breaking changes occur between `2.0.0-rc.0` and the final `2.0.0` release.
> 
> These are interpreted as: `>=2.0.0-rc.0, <3.0.0`

Personally I got interested in this when cargo-deny [reported](https://github.com/EmbarkStudios/cargo-deny/issues/316) a vulnerability in trust-dns-resolver 0.20.0-alpha.3 which actually had been solved in 0.4.3 and 0.5.0-alpha.3.

However, I also had issues where cargo update would update my workspace from trust-dns-resolver 0.20.0-alpha.3 to trust-dns-resolver 0.20.0. 0.20.0 final used tokio 1.0 whereas the alpha was still on an earlier version, so that was not a trivial update -- this is not just a vulnerability reporting issue.

## Proposed solution

Cargo should consider pre-releases as incompatible with "stable" releases:

- `^3.0.0-alpha.1` does not match `3.0.0-alpha.2`
- `^3.0.0-alpha.1` does not match `3.0.0-beta.1`
- `^3.0.0-alpha.1` does not match `3.0.0`

## Context

@dtolnay as current maintainer of semver has stated:

> Now if Cargo were to decide to change its interpretation of pre-release reqs in a future version (via a Cargo RFC), this crate would follow suite, since the scope is explicitly to implement Cargo's interpretation of SemVer. But neither of us had the impression that was likely to happen.

Therefore I would like to bring this discussion to the Cargo team.

cc @ehuss @Eh2406

---

<div class="post-metadata">

**Author:** ![matklad](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/matklad/32/12266_2.png) [@matklad](https://internals.rust-lang.org/u/matklad)\
**Post date:** [June 3, 2021, 8:55am UTC](https://internals.rust-lang.org/t/changing-cargo-semver-compatibility-for-pre-releases/14820/2 "2021-06-03T08:55:14Z")

</div>

> [@djc](#):
>
> `^2` matches `2.3-beta.1` , but not `3.0.0-alpha.1`

Is this a typo? Or is it really intended that, if I have `foo="2"`, I might pull a pre-release?

---

<div class="post-metadata">

**Author:** ![matklad](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/matklad/32/12266_2.png) [@matklad](https://internals.rust-lang.org/u/matklad)\
**Post date:** [June 3, 2021, 8:57am UTC](https://internals.rust-lang.org/t/changing-cargo-semver-compatibility-for-pre-releases/14820/3 "2021-06-03T08:57:33Z")

</div>

> [@djc](#):
>
> `^3.0.0-alpha.1` does not match `3.0.0-alpha.2`

What would be the difference between `=3.0.0-alpha.1` and `^3.0.0-alpha.1`?

---

<div class="post-metadata">

**Author:** ![djc](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/djc/32/1592_2.png) [@djc](https://internals.rust-lang.org/u/djc)\
**Post date:** [June 3, 2021, 9:02am UTC](https://internals.rust-lang.org/t/changing-cargo-semver-compatibility-for-pre-releases/14820/4 "2021-06-03T09:02:33Z")

</div>

> [@matklad](#):
>
> Is this a typo? Or is it really intended that, if I have `foo="2"` , I might pull a pre-release?

Let's call it a thinko. Removed it for now.

> [@matklad](#):
>
> What would be the difference between `=3.0.0-alpha.1` and `^3.0.0-alpha.1` ?

I think there probably should not be a difference?

---

<div class="post-metadata">

**Author:** ![matklad](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/matklad/32/12266_2.png) [@matklad](https://internals.rust-lang.org/u/matklad)\
**Post date:** [June 3, 2021, 9:15am UTC](https://internals.rust-lang.org/t/changing-cargo-semver-compatibility-for-pre-releases/14820/5 "2021-06-03T09:15:57Z")

</div>

I’ve had a related grievance: one my dependencies depended on a pre-release package. That caused `cargo update` to break my code, as that transitive dep got changed in the next pre-release. My understanding is that this is allowed by semver — requirements are waived for pre-releases, so that this mechanism can be used to test-drive the API before committing to it. What didn’t work out well is that, as a maintainer of the leaf application with Cargo.lock, I didn’t know that my dep opted me into instability.

---

<div class="post-metadata">

**Author:** ![Shnatsel](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/shnatsel/32/4613_2.png) [@Shnatsel](https://internals.rust-lang.org/u/Shnatsel)\
**Post date:** [June 3, 2021, 10:55am UTC](https://internals.rust-lang.org/t/changing-cargo-semver-compatibility-for-pre-releases/14820/6 "2021-06-03T10:55:29Z")

</div>

> [@djc](#):
>
> Personally I got interested in this when cargo-deny [reported](https://github.com/EmbarkStudios/cargo-deny/issues/316) a vulnerability in trust-dns-resolver 0.20.0-alpha.3 which actually had been solved in 0.4.3 and 0.5.0-alpha.3.

I am working on rewriting the vulnerability matching logic in `rustsec` crate in order to fix this. Here's my perspective.

The _version requirements_ used e.g. by Cargo and implemented by `semver` crate are distinct from _version ranges._ It appears that for a _version requirement_ it is a deliberate design decision not to match pre-release versions, so that a requirement such as `>= 2.0.0` that did not explicitly opt in to pre-release versions doesn't receive them.

The use case for `rustsec` crate matching of vulnerable versions is genuinely different. We need to track version intervals e.g. from 0.6.0 to 0.6.5, but really don't care if any of the versions within that interval are pre-releases or not. From the spec this only uses the [precedence rules](https://semver.org/#spec-item-11), not the version matching logic.

This extra logic around pre-releases also unexpectedly shows up in other places, causing trouble. For example, deps.rs will [report pre-release versions as out of date](https://user-images.githubusercontent.com/797/107662451-29812100-6c3f-11eb-8a67-93d654740437.png).

**The distinction is subtle, and communicating about it is tricky.** I know of at least one maintainer who was burned out on their entire project while trying to resolve this, both by communicating with the upstream (unsuccessfully) and by implementing custom logic (repeatedly broken by semver crate API changes).

The best solution I can see is semver crate implementing a range matching logic based on precedence only, since there appears to be demand for it. There even appears to be a framework for it already in place - prior to 1.0 `semver` crate also supported the node.js SemVer flavor.

At this point I don't really care because I'm rolling my own version matching logic regardless, but it could be useful for other consumers of the semver crate.

---

<div class="post-metadata">

**Author:** ![jplatte](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/jplatte/32/11437_2.png) [@jplatte](https://internals.rust-lang.org/u/jplatte)\
**Post date:** [June 3, 2021, 10:55am UTC](https://internals.rust-lang.org/t/changing-cargo-semver-compatibility-for-pre-releases/14820/7 "2021-06-03T10:55:29Z")

</div>

I also find it annoying that for prereleases you have to use `=x.y.z` as a version req to avoid breakage in `cargo update`. However, I've also heard from the actix(-web) devs that they intentionally didn't use exact version requirements because that would mean more work to publish a new lower-level crate prerelease (now have to re-release all the reverse deps even if there were no breaking changes that affected them).

I think nobody really wants to write `>=1.0.0-beta.1 <2.0.0`, so maybe an alternative could be that `^1.0.0-beta.1` keeps its current behavior, but `1.0.0-beta.1` changes to be equivalent to `=1.0.0-beta.1`?

---

<div class="post-metadata">

**Author:** ![Eh2406](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/eh2406/32/4839_2.png) [@Eh2406](https://internals.rust-lang.org/u/Eh2406)\
**Post date:** [June 3, 2021, 5:29pm UTC](https://internals.rust-lang.org/t/changing-cargo-semver-compatibility-for-pre-releases/14820/8 "2021-06-03T17:29:51Z")

</div>

Before I can meaningfully think about what a good behavior would be, I wonder what is a practical way to change this? Will it brake existing lockfiles? If it is a braking change how will that be rolled out?

---

<div class="post-metadata">

**Author:** ![kornel](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/kornel/32/2711_2.png) [@kornel](https://internals.rust-lang.org/u/kornel)\
**Post date:** [June 4, 2021, 3:15pm UTC](https://internals.rust-lang.org/t/changing-cargo-semver-compatibility-for-pre-releases/14820/9 "2021-06-04T15:15:43Z")

</div>

I'm fine with the current behavior. Pre-release versions behave as if they existed in their own semver-major version, and that makes sense in practice.

Certainly it makes sense that `>= 2.0.0` doesn't match `2.0.0-rc.0`, because pre-release means it hasn't been released yet. In semver sort order it's true that `2.0.0-rc.0 < 2.0.0`.

Pre-release versions may be unstable, and `>= 2.0.0` names a stable version. It's not explicitly opting to alpha/beta versions. So even though `3.0.0-beta` is a higher version, it's an unstable version. It could be breaking to opt-in users to unstable versions when they don't ask explicitly.

IMHO in this case it's just a wrong implementation in cargo-audit. Instead of `matches` it should probably use `Ord` or have its own equivalent of `matches` that is optimized for describing release ranges, rather than matching versions to version requirements.

---

<div class="post-metadata">

**Author:** ![dtolnay](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/dtolnay/32/1447_2.png) [@dtolnay](https://internals.rust-lang.org/u/dtolnay)\
**Post date:** [June 4, 2021, 4:38pm UTC](https://internals.rust-lang.org/t/changing-cargo-semver-compatibility-for-pre-releases/14820/10 "2021-06-04T16:38:20Z")

</div>

> [@jplatte](#):
>
> maybe an alternative could be that `^1.0.0-beta.1` keeps its current behavior, but `1.0.0-beta.1` changes to be equivalent to `=1.0.0-beta.1`

This, I think, actually has a chance if someone were to write a good Cargo RFC.

---

<div class="post-metadata">

**Author:** ![Shnatsel](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/shnatsel/32/4613_2.png) [@Shnatsel](https://internals.rust-lang.org/u/Shnatsel)\
**Post date:** [June 4, 2021, 4:40pm UTC](https://internals.rust-lang.org/t/changing-cargo-semver-compatibility-for-pre-releases/14820/11 "2021-06-04T16:40:40Z")

</div>

Fun fact: the behavior of selectors such as `^` is **not specified** anywhere in the [semver 2.0 spec](https://semver.org/).

It does specify [precedence rules](https://semver.org/#spec-item-11), but that's not the same as selectors and handles pre-releases very differently.

---

<div class="post-metadata">

**Author:** ![mbrubeck](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/mbrubeck/32/174_2.png) [@mbrubeck](https://internals.rust-lang.org/u/mbrubeck)\
**Post date:** [June 4, 2021, 4:52pm UTC](https://internals.rust-lang.org/t/changing-cargo-semver-compatibility-for-pre-releases/14820/12 "2021-06-04T16:52:40Z")

</div>

Cargo's semver "range" syntax was originally based on [NPM semver ranges](https://docs.npmjs.com/cli/v6/using-npm/semver/), though Cargo supports only a subset of the NPM syntax and [there is no plan to implement the full NPM spec](https://github.com/dtolnay/semver/issues/58).

---

<div class="post-metadata">

**Author:** ![Shnatsel](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/shnatsel/32/4613_2.png) [@Shnatsel](https://internals.rust-lang.org/u/Shnatsel)\
**Post date:** [June 13, 2021, 6:31pm UTC](https://internals.rust-lang.org/t/changing-cargo-semver-compatibility-for-pre-releases/14820/13 "2021-06-13T18:31:53Z")

</div>

Is there a specification of the known operators and the semantics of each operator? Or rather, does [this](https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html) list the full range of known operators?

I have found myself [reimplementing the behavior of operators](https://github.com/RustSec/rustsec/pull/366/files#diff-38801777e582c0b36a92ba2a4ac7e24527df8fc8e6051af1adb35ef46142fab4R127) because I need to turn version requirements into version ranges, and `semver` crate doesn't support that. A specification would be very helpful.

---

<div class="post-metadata">

**Author:** ![mbrubeck](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/mbrubeck/32/174_2.png) [@mbrubeck](https://internals.rust-lang.org/u/mbrubeck)\
**Post date:** [June 14, 2021, 4:33am UTC](https://internals.rust-lang.org/t/changing-cargo-semver-compatibility-for-pre-releases/14820/14 "2021-06-14T04:33:46Z")

</div>

Yes, I believe the Cargo reference's list of operators is complete. The corresponding code is [here](https://github.com/dtolnay/semver/blob/8fc2cfb41ff020a800a9f0fc113e0bbb2fd3a5f0/src/parse.rs#L260-L283).

Note that the special treatment of pre-release identifiers means that a `VersionReq` string like `">= 1.0.0"` does not match versions like `"2.0.0-alpha"`, so treating a `VersionReq` as a range will not precisely match the Cargo/NPM behavior. It may still be a reasonable behavior for the advisory DB, though.

---

<div class="post-metadata">

**Author:** ![Shnatsel](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/shnatsel/32/4613_2.png) [@Shnatsel](https://internals.rust-lang.org/u/Shnatsel)\
**Post date:** [June 14, 2021, 11:44am UTC](https://internals.rust-lang.org/t/changing-cargo-semver-compatibility-for-pre-releases/14820/15 "2021-06-14T11:44:15Z")

</div>

Thank you!

Yes, indeed `">= 1.0.0"` not matching versions like `"2.0.0-alpha"` is an issue for the advisory DB, but is probably reasonable behavior for Cargo. This is one of the reasons why I'm writing my own implementation of version matching.

---

<div class="post-metadata">

**Author:** ![system](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/system/32/14092_2.png) [@system](https://internals.rust-lang.org/u/system)\
**Post date:** [September 12, 2021, 11:44am UTC](https://internals.rust-lang.org/t/changing-cargo-semver-compatibility-for-pre-releases/14820/16 "2021-09-12T11:44:45Z")

</div>

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.
