Skip to content

RFC: Make Cargo respect minimum supported Rust version (MSRV) when selecting dependencies #3537

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 184 commits into from
Feb 28, 2024
Merged
Changes from all commits
Commits
Show all changes
184 commits
Select commit Hold shift + click to select a range
fb56964
chore: Add skeleton
epage Nov 14, 2023
b41b342
feat: Initial draft
epage Nov 14, 2023
2ace3a0
fix: Add missing connector statement
epage Nov 15, 2023
42b6853
fix: Clarify the stagnation drawback
epage Nov 15, 2023
993f52f
feat: Acknowledge CI incompatibility change
epage Nov 15, 2023
404ec9a
feat: Acknowledge desire to set msrv more
epage Nov 15, 2023
b757cb7
feat: Cover workspace.rust-version
epage Nov 15, 2023
2da8375
fix: Expand on implicit MSRV
epage Nov 15, 2023
72f7bef
fix: Be more explicit about rustc fallback
epage Nov 15, 2023
44c28b4
fix: Add another design consideration
epage Nov 15, 2023
f663bf2
feat: Add 'rustc --version' fallback to proposal
epage Nov 15, 2023
2fc50a2
fix: Small language clarifications
epage Nov 17, 2023
01feadc
fix: Minor cleanup
epage Nov 28, 2023
4547449
feat: Add Guide
epage Nov 28, 2023
6a704c9
feat: Update proposal for new config
epage Nov 28, 2023
f534b34
fix: Typo
epage Nov 28, 2023
e794db9
fix: Cover why we use config
epage Nov 28, 2023
9a2b85c
fix: Make explicit that feature-specific MSRVs can still work
epage Nov 28, 2023
5a10cd6
fix: Mention the backtracking trade off
epage Nov 28, 2023
260ef18
fix: Mention rustup MSRV support
epage Nov 28, 2023
ac35d97
fix: Cover cargo-unrelated MSRV improvements
epage Nov 28, 2023
ac9aef6
fix: Mention Python's support window
epage Nov 29, 2023
6d85ec9
fix: Add PreRFC link
epage Nov 29, 2023
66c04c0
fix: Add RFC number
epage Nov 29, 2023
736060a
feat: Acknowledge a role in MSRV policy setting
epage Nov 29, 2023
6de5f4d
fix: Clarify wording
epage Nov 29, 2023
76d2f98
fix: Update future possibility for config change
epage Nov 29, 2023
c668698
fix: Simplify config
epage Nov 29, 2023
3015bd3
fix: Typos
epage Nov 29, 2023
2db39c0
fix: Typo
epage Nov 29, 2023
5c55bd1
fix: Separate top-level selection from deps clearer
epage Nov 29, 2023
4fc3608
fix: Extend future possibility with rust-version-policy field
epage Nov 29, 2023
0228ae7
fix: Typo
epage Nov 30, 2023
d418634
fix: Typo
epage Nov 30, 2023
8eaf4dd
fix: Actually make sense when discussing precedence without MSRV set
epage Nov 30, 2023
e54af30
fix: Call out rust-toolchain.toml
epage Nov 30, 2023
5b6b306
fix: Update stale references for proposed config
epage Nov 30, 2023
413ee13
fix: Call out use of 'cargo update' for CI
epage Nov 30, 2023
974f233
fix: Be consistent when naming MSRV workflows
epage Nov 30, 2023
b5d0cef
fix: Clarify and define workflow terms
epage Nov 30, 2023
4deef7f
fix: Use existing issue for tracking issue
epage Nov 30, 2023
065c97f
fix: Talk through more extensive reporting
epage Dec 5, 2023
66ee8bf
feat: Provide examples for why people are on old versions
epage Dec 7, 2023
1d507f1
fix: Capture matklad's feedback on testing latest deps
epage Dec 7, 2023
538167b
fix: Shift most of the CI discussion from Alt to Drawback
epage Dec 7, 2023
3419057
fix: Add another old-MSRV reason
epage Dec 7, 2023
f597ea3
refactor: Make most pertinent alternatives more obvious
epage Dec 7, 2023
05d86e4
fix: Try to clarify the multi-MSRV situations
epage Dec 7, 2023
c60d85e
fix: Call out hard-error's problem with feature-dependent MSRV
epage Dec 7, 2023
98db27a
fix: Cover rust-toolchain file
epage Dec 7, 2023
fc0d66e
fix: Apply feedback to 'resolve with rustc version'
epage Dec 7, 2023
593d56d
feat: Define 'support'
epage Dec 7, 2023
8f0c0cf
feat: Add PHP prior art
epage Dec 12, 2023
1514401
fix: Clarify misc section
epage Dec 14, 2023
b268e45
Be explicit about impact for verifying latest dependencies
epage Dec 14, 2023
ff544c4
fix: Emphasize, rather than yellow
epage Dec 14, 2023
447048d
feat: Expand on opt-in
epage Dec 12, 2023
ca225b9
rustc
epage Dec 14, 2023
2381b9d
fix: Clarify the fallback to rustc --version
epage Dec 15, 2023
c874e2b
fix: Clear up language on cargo config
epage Dec 15, 2023
f4cb627
fix: Clarify the scope of a drawback
epage Dec 15, 2023
f2c1e86
fix: Soften unintentionally strong language on minor bumps
epage Dec 15, 2023
94b0bf3
fix: Clarify why minor is suggested
epage Dec 15, 2023
df3f275
fix: Remove reason against tying to edition
epage Dec 15, 2023
6f1e52a
fix: Clarify role of CI
epage Dec 15, 2023
718a15a
fix: Make a sentence a little less redundant
epage Jan 3, 2024
914d764
refactor(motivation): Re-center on common workflows
epage Jan 7, 2024
2a768c9
fix(motivation): Call out role of rust-toolchain.toml file
epage Jan 8, 2024
6addf93
fix(motivation): Cover verifying multiple versions
epage Jan 8, 2024
4e7c0d0
fix(motivation): Don't have two workflows at same priority
epage Jan 8, 2024
d8bcc36
fix(motivation): Clarify the structure
epage Jan 10, 2024
6d15b97
fix(motivation): Minor expansion on MSRV workarounds
epage Jan 10, 2024
a6bd027
fix(motivation): Clarify and expand on workflows
epage Jan 10, 2024
f99ecac
fix(ref): Call out stabilization plan
epage Jan 10, 2024
f1cb6bf
feat(ref): Tie the solution to an Edition
epage Jan 10, 2024
541580c
feat(ref): Add perma-setting for '--ignore-rust-version' for builds
epage Jan 11, 2024
ca32ebc
feat(ref): Add '--update-rust-version' to help users 'progress'
epage Jan 11, 2024
e44a724
feat(ref): Add 'rust-version=auto'
epage Jan 11, 2024
941b0a3
refactor(rationale): Re-order sections
epage Jan 11, 2024
4e0a359
fix(unresolved): Add another alt for config field
epage Jan 11, 2024
2f709d6
fix(future): Clarify why cargo-audit
epage Jan 11, 2024
d52fdb3
refactor(rationale): Re-work resolver discussion around workflows
epage Jan 11, 2024
8a6bc9b
fix(motivation): Better balance stagnation-avoidance
epage Jan 11, 2024
12eb56f
fix(motivation): Soften language to not shame bugs
epage Jan 11, 2024
2504124
fix(motivation): Clarify that lockfile changes are restricted to dep …
epage Jan 11, 2024
2365d6f
fix(motivation): Grammar
epage Jan 11, 2024
5a8ed3d
fix(motivation): Call out dependent impact on no-msrv
epage Jan 11, 2024
165d447
fix(motivation): Clarify toolchain problem with latest msrv
epage Jan 11, 2024
a98f54a
fix(motivation): Add list separator
epage Jan 12, 2024
c2b70e5
fix(motivation): Re-order principles to soften it
epage Jan 12, 2024
b28ffd2
fix(motivation): Soften costs talk
epage Jan 12, 2024
c5a7d63
fix(motivation): Bridge global MSRV to exceptions
epage Jan 12, 2024
f390368
fix(motivation): Typo
epage Jan 12, 2024
ba2acae
fix(motivation): Clarify dev-dependencies workflow
epage Jan 12, 2024
9b119eb
fix(ref): Soften language on cargo-update
epage Jan 12, 2024
eebb556
fix(ref): Clarify reasoning for behavior
epage Jan 12, 2024
62a217f
fix(ref): Soften cargo-add language
epage Jan 12, 2024
a886c92
fix(ref): Move config by resolver
epage Jan 12, 2024
330eee4
fix(ref): Update config section
epage Jan 12, 2024
590e085
fix(rationale): Add more to why aggressive MSRV is fine
epage Jan 12, 2024
e2128c4
fix(rationale): Clean up grammar
epage Jan 12, 2024
8171e5f
fix(rationale): Clarify why config is still needed
epage Jan 12, 2024
037300b
fix(rationale): Clean up alternative comparison
epage Jan 12, 2024
7cf62af
fix(ref): Flatten the config
epage Jan 12, 2024
d54dbb1
fix(future): 1.75 is now past tense
epage Jan 12, 2024
1eca864
fix(motivation): Transfer use cases to priority description
epage Jan 13, 2024
fdb539f
style(alt): Line wrap paragraph
epage Jan 15, 2024
4e269a5
feat(alt): Expand on rust-version-policy
epage Jan 15, 2024
e87c4be
feat(future): Include cargo-publish issue
epage Jan 16, 2024
3990895
fix: Typo
epage Jan 19, 2024
9e76b7c
fix(ref): Be more specific on auto behavior
epage Jan 19, 2024
351ec04
feat(ref): Mirror new 'update' flags on 'generate-lockfile'
epage Jan 19, 2024
9b592cb
fix(alt): Clarify reporting's tree change meaning
epage Jan 19, 2024
3108268
fix(alt): Cover Reporting for no Cargo.lock
epage Jan 19, 2024
b1b8f2b
fix(unresolved): Call out reporting level for stabilization process
epage Jan 19, 2024
5efca85
fix(ref): Be more explicit about auto behavior
epage Jan 20, 2024
7f9355a
fix(guide): Clean up language in config docs
epage Jan 20, 2024
8d083f8
fix(guide): Fix direction of comparison
epage Jan 20, 2024
8753df1
fix(ref): Be explicit that old dependencies are preferred
epage Jan 20, 2024
40e9374
fix(summary): Call out the MSRV data help
epage Jan 20, 2024
1c66807
fix(ref): Be more explicit on update behavior
epage Jan 22, 2024
5b360f9
feat(deferred): Defer resolving `auto`s name
epage Jan 23, 2024
bb90a15
fix(deferred): Expand on config
epage Jan 23, 2024
ce36cf2
feat(rationale): Discuss --ignore-rust-version and compilation
epage Jan 23, 2024
c44a063
fix(alt): Typo
epage Jan 23, 2024
ee1f87e
fix(guide): Split out documentation updates section
epage Jan 23, 2024
6b06734
fix(future): Maybe caution about unspecified MSRVs?
epage Jan 23, 2024
505224a
fix(ref): Nightly publish with auto should warn
epage Jan 23, 2024
6c17440
fix(ref): Improve compilation diagnostic
epage Jan 24, 2024
0bc42a8
fix(ref): Promote report on every command from alt to ref, but keep i…
epage Jan 24, 2024
242bc19
feat(guide): Step through this RFC with different users/workflows
epage Jan 24, 2024
18a2683
fix(motivation): Tweak lockfile language
epage Jan 24, 2024
9e2641d
fix: Correct suggested rustup command
epage Feb 2, 2024
c68b072
fix: Remove extra word
epage Feb 2, 2024
6458c0c
fix: Clarify -n is dry-run
epage Feb 2, 2024
3ee143a
fix: Clarify 'we' in pain points
epage Feb 2, 2024
064fdac
fix(ref): Clarify transition for reporting stale dependencies
epage Feb 2, 2024
7c30483
fix(guide): Include auto in documentation update
epage Feb 2, 2024
30049fe
fix(motivation): Add status quo header
epage Feb 2, 2024
7b84264
fix(ref): Remove stale comment
epage Feb 2, 2024
1244f87
fix(ref): Clarify held-back reporting
epage Feb 2, 2024
aae359c
fix(ref): Clarify rust-version statement
epage Feb 2, 2024
9b9390e
fix(drawback): Remind people about limtis of workspace.resolver warning
epage Feb 2, 2024
ec79f52
fix(rationale): Remove stale rationale
epage Feb 2, 2024
1dab410
fix(rationale): Clarify rust-version and minimal version interaction
epage Feb 2, 2024
43db971
fix(rationale): Clarify rust-version and minimal version interaction
epage Feb 2, 2024
5294aba
fix(rationale): Clarify workspace-level lint for incompatible MSRV
epage Feb 2, 2024
382cb1d
fix(rationale): Switch how we highlight different cases for ignore-ru…
epage Feb 2, 2024
7542afc
fix(rationale): Clarify sentence for update-rust-version
epage Feb 2, 2024
1896b00
fix(rationale): Clarify section on auto
epage Feb 2, 2024
fda1f88
fix(guide): Collapse workflow step throughs
epage Feb 8, 2024
da29fce
fix(ref): Switch nightly auto to unset on publish
epage Feb 8, 2024
1a97aef
refactor(guide): Move example workflows later
epage Feb 8, 2024
14dc48b
fix(guide): Clean up rust-version documentation
epage Feb 8, 2024
3923454
fix(guide): Add a summary
epage Feb 8, 2024
040a981
fix(motivation): Highlight which are pros and cons
epage Feb 15, 2024
d08e04f
fix(ref): Add missing context for resolve diffing
epage Feb 15, 2024
9977612
fix(ref): Specify rust-version fallback when on pre-release
epage Feb 15, 2024
cf4651b
fix(alt): Clarify why a CLI option isn't used
epage Feb 15, 2024
13fcb24
fix(alt): Further clarify CLI option
epage Feb 15, 2024
ef57daa
fix: Space out code fences
epage Feb 15, 2024
dce5d7c
fix(motivation): Include rust-version proliferation stats
epage Feb 20, 2024
abd9a5e
fix(motivation): Clarify scope of MSRV meaning
epage Feb 23, 2024
2ddb847
fix: Yeet the term 'auto' to avoid confusion
epage Feb 23, 2024
74651c9
fix(motivation): Make sure we call out security explicitly
epage Feb 23, 2024
a33ea45
fix(rationale): Include short-term benefit of polyfills
epage Feb 23, 2024
ff7d7ae
fix: Don't confuse markdown parsers treating items like HTML
epage Feb 23, 2024
895545c
feat(future): Call out tracking of maintenance status
epage Feb 23, 2024
426757f
fix(guide): Remove reference to MSRV is 'can be compiled'
epage Feb 23, 2024
9f22fbc
fix: Typo
epage Feb 27, 2024
03431f9
fix: Typo
epage Feb 27, 2024
20c2bbf
fix: Typo
epage Feb 27, 2024
5379658
fix: Typo
epage Feb 27, 2024
551b739
fix: Typo
epage Feb 27, 2024
55ca765
fix: Minor wording change
epage Feb 27, 2024
1529093
fix: Typo
epage Feb 27, 2024
d20e3ee
fix: Typo
epage Feb 27, 2024
0de0ccd
fix: Typo
epage Feb 27, 2024
08015f3
fix: Typo
epage Feb 27, 2024
fef356e
fix: Typo
epage Feb 27, 2024
7c9f7fc
fix: Typo
epage Feb 27, 2024
a8a3c1e
fix: Clarify language
epage Feb 27, 2024
16912fe
fix: Clarify language
epage Feb 27, 2024
b2947e3
feat(summary): Remind people of RFC fluidity
epage Feb 27, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,306 changes: 1,306 additions & 0 deletions text/3537-msrv-resolver.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,1306 @@
- Feature Name: `msrv-resolver`
- Start Date: 2023-11-14
- Pre-RFC: [internals](https://internals.rust-lang.org/t/pre-rfc-msrv-aware-resolver/19871)
- RFC PR: [rust-lang/rfcs#3537](https://github.com/rust-lang/rfcs/pull/3537)
- Rust Issue: [rust-lang/cargo#9930](https://github.com/rust-lang/cargo/issues/9930)

# Summary
[summary]: #summary

Provide a happy path for developers needing to work with older versions of Rust by
- Preferring MSRV (minimum-supported-rust-version) compatible dependencies when Cargo resolves dependencies
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Darksonn

The fact that resolver.precedence=rustc is not the default option is very surprising to me, and it's something I missed when I first looked at this RFC.

(moved from here to avoid interleaved conversations)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you help me understand where this confusion came from? Through the curse of knowledge, I'm having a hard time understanding how that happened and would like to see how I could improve the RFC.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When I specify an older MSRV in my Cargo.toml, I generally have two configurations that I test in CI:

  1. Latest Rust with latest dependencies.
  2. MSRV rust with MSRV-compatible dependencies.

The first configuration is the most important one, since that is what most of the users will be using. So generally, I would have one CI run with MSRV-Rust, and run all other CI jobs with latest Rust. I usually don't check in a Cargo.lock, but if I did, then I would check in the first configuration, as it is the most important one.

Therefore, it seems weird to me that the tool would pick MSRV-compatible dependencies when I am using the latest Rust, as that is a signal that I'm currently using the latest-dependencies configuration.

As for how you could have changed the RFC so that I didn't miss this ... I admit to having only skimmed it the first time. I simply saw the RFC, got excited because this is something I've wanted for a long time, and made the assumption that the behavior matched what I would have written myself. If you had included some examples of the new behavior near the beginning, I would probably have noticed there.

Tokio currently relies on the fact that "latest dependencies" == "MSRV-compatible dependencies" and removes dependencies that break this property. This feature would let us remove that requirement, so I am very much looking forward to it.

- Ensuring compatible version requirements when `cargo add` auto-selects a version
- Smoothing out the path for setting and maintaining a verified MSRV so the above will be more likely to pick a working version.

Note: `cargo install` is intentionally left out for now to decouple discussions on how to handle the security ramifications.

**Note:** Approval of this RFC does not mean everything is set in stone, like with all RFCs.
This RFC will be rolled out gradually as we stabilize each piece.
In particular, we expect to make the `cargo new` change last as it is dependent on the other changes to work well.
In evaluating stabilization, we take into account changes in the ecosystem and feedback from testing unstable features.
Based on that evaluation, we may make changes from what this RFC says.
Whether we make changes or not, stabilization will then require approval of the cargo team to merge
(explicit acknowledgement from all but 2 members with no concerns from any member)
followed by a 10 days Final Comment Period (FCP) for the remaining 2 team members and the wider community.
Cargo FCPs are now tracked in This Week in Rust to ensure the community is aware and can participate.
Even then, a change like `cargo new` can be reverted without an RFC,
likely only needing to follow the FCP process.

# Motivation
[motivation]: #motivation

## Status Quo

<details><summary>Ensuring you have a `Cargo.lock` with dependencies compatible with your minimum-supported Rust version (MSRV) is an arduous task of running `cargo update <dep> --precise <ver>` until it works.</summary>

Let's step through a simple scenario where a user develops with the
latest Rust version but production uses an older version:
```console
$ cargo new msrv-resolver
Created binary (application) `msrv-resolver` package
$ cd msrv-resolver
$ # ... add `package.rust-version = "1.64.0"` to `Cargo.toml`
$ cargo add clap
Updating crates.io index
Adding clap v4.4.8 to dependencies.
Features:
...
Updating crates.io index
$ git commit -a -m "WIP" && git push
...
```

After 30 minutes, CI fails.
The first step is to reproduce this locally
```console
$ rustup install 1.64.0
...
$ cargo +1.64.0 check
Updating crates.io index
Fetch [===============> ] 67.08%, (28094/50225) resolving deltas
```

After waiting several minutes, cursing being stuck on a version from before sparse registry support was added...
```console
$ cargo +1.64.0 check
Updating crates.io index
Downloaded clap v4.4.8
Downloaded clap_builder v4.4.8
Downloaded clap_lex v0.6.0
Downloaded anstyle-parse v0.2.2
Downloaded anstyle v1.0.4
Downloaded anstream v0.6.4
Downloaded 6 crates (289.3 KB) in 0.35s
error: package `clap_builder v4.4.8` cannot be built because it requires rustc 1.70.0 or newer, while the currently ac
tive rustc version is 1.64.0
```

Thankfully, crates.io now shows [supported Rust versions](https://crates.io/crates/clap_builder/versions), so I pick v4.3.24.
```console
$ cargo update -p clap_builder --precise 4.3.24
Updating crates.io index
error: failed to select a version for the requirement `clap_builder = "=4.4.8"`
candidate versions found which didn't match: 4.3.24
location searched: crates.io index
required by package `clap v4.4.8`
... which satisfies dependency `clap = "^4.4.8"` (locked to 4.4.8) of package `msrv-resolver v0.1.0 (/home/epage/src/personal/dump/msrv-resolver)`
perhaps a crate was updated and forgotten to be re-vendored?
```

After browsing on some forums, I edit my `Cargo.toml` to roll back to `clap = "4.3.24"` and try again
```console
$ cargo update -p clap --precise 4.3.24
Updating crates.io index
Downgrading anstream v0.6.4 -> v0.3.2
Downgrading anstyle-wincon v3.0.1 -> v1.0.2
Adding bitflags v2.4.1
Downgrading clap v4.4.8 -> v4.3.24
Downgrading clap_builder v4.4.8 -> v4.3.24
Downgrading clap_lex v0.6.0 -> v0.5.1
Adding errno v0.3.6
Adding hermit-abi v0.3.3
Adding is-terminal v0.4.9
Adding libc v0.2.150
Adding linux-raw-sys v0.4.11
Adding rustix v0.38.23
$ cargo +1.64.0 check
Downloaded clap_builder v4.3.24
Downloaded errno v0.3.6
Downloaded clap_lex v0.5.1
Downloaded bitflags v2.4.1
Downloaded clap v4.3.24
Downloaded rustix v0.38.23
Downloaded libc v0.2.150
Downloaded linux-raw-sys v0.4.11
Downloaded 8 crates (2.8 MB) in 1.15s (largest was `linux-raw-sys` at 1.4 MB)
error: package `anstyle-parse v0.2.2` cannot be built because it requires rustc 1.70.0 or newer, while the currently a
ctive rustc version is 1.64.0
```

Again, consulting [crates.io](https://crates.io/crates/anstyle-parse/versions)
```console
$ cargo update -p anstyle-parse --precise 0.2.1
Updating crates.io index
Downgrading anstyle-parse v0.2.2 -> v0.2.1
$ cargo +1.64.0 check
error: package `clap_lex v0.5.1` cannot be built because it requires rustc 1.70.0 or newer, while the currently active
rustc version is 1.64.0
```

Again, consulting [crates.io](https://crates.io/crates/clap_lex/versions)
```console
$ cargo update -p clap_lex --precise 0.5.0
Updating crates.io index
Downgrading clap_lex v0.5.1 -> v0.5.0
$ cargo +1.64.0 check
error: package `anstyle v1.0.4` cannot be built because it requires rustc 1.70.0 or newer, while the currently active
rustc version is 1.64.0
```

Again, consulting [crates.io](https://crates.io/crates/anstyle/versions)
```console
cargo update -p anstyle --precise 1.0.2
Updating crates.io index
Downgrading anstyle v1.0.4 -> v1.0.2
$ cargo +1.64.0 check
Downloaded anstyle v1.0.2
Downloaded 1 crate (14.0 KB) in 0.60s
Compiling rustix v0.38.23
Checking bitflags v2.4.1
Checking linux-raw-sys v0.4.11
Checking utf8parse v0.2.1
Checking anstyle v1.0.2
Checking colorchoice v1.0.0
Checking anstyle-query v1.0.0
Checking clap_lex v0.5.0
Checking strsim v0.10.0
Checking anstyle-parse v0.2.1
Checking is-terminal v0.4.9
Checking anstream v0.3.2
Checking clap_builder v4.3.24
Checking clap v4.3.24
Checking msrv-resolver v0.1.0 (/home/epage/src/personal/dump/msrv-resolver)
Finished dev [unoptimized + debuginfo] target(s) in 2.96s
```

Success! Mixed with many tears and less hair.

</details>

How wide spread is this? Take this with a grain of salt but based on crates.io user agents:

| Common MSRVs | % Compatible Requests |
|--------------------:|:----------------------|
| N (`1.73.0`) | 47.432% |
| N-2 (`1.71.0`) | 74.003% |
| ~6 mo (`1.69.0`) | 93.272% |
| ~1 year (`1.65.0`) | 98.766% |
| Debian (`1.63.0`) | 99.106% |
| ~2 years (`1.56.0`) | 99.949% |

*([source](https://rust-lang.zulipchat.com/#narrow/stream/318791-t-crates-io/topic/cargo.20version.20usage/near/401440149))*

This was aided by the presence of `package.rust-version`.
Of all packages (137,569), only 8,857 (6.4%) have that field set.
When limiting to the 61,758 "recently" published packages (an upload since the start of 2023),
only 8,550 (13.8%) have the field set.

People have tried to reduce the pain from MSRV with its own costs:
- Treating it as a breaking change:
- This leads to extra churn in the ecosystem when a fraction of users are likely going to benefit
- We have the precedence elsewhere in the Rust ecosystem for build and runtime system requirement changes not being breaking, like when rustc requires newer versions of glibc, Android NDK, etc.
- Adding upper limits to version requirements:
- This fractures the ecosystem by making packages incompatible with each other and the Cargo team [discourages doing this](https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html#multiple-requirements)
- Avoiding dependencies, re-implementing it themselves at the cost of their time and the risk for bugs, especially if `unsafe` is involved
- Ensuring dependencies have a more inclusive MSRV policy then themselves
- This has lead to long arguments in the ecosystem over what is the right
policy for updating a minimum-supported Rust version (MSRV),
wearing on all
(e.g.
[libc](https://github.com/rust-lang/libs-team/issues/72),
[time](https://github.com/time-rs/time/discussions/535)
)

The sooner we improve the status quo, the better, as it can take years for
these changes to percolate out to those exclusively developing with an older
Rust version (in contrast with the example above).
This delay can be reduced somewhat if a newer toolchain can be used for
development version without upgrading the MSRV.

## Workflows
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@LukeMathWalker

I wanted to leave targeted feedback on the RFC paragraphs, but I'm struggling since it isn't structured as I would expect it to. You've identified workflows, but even before that I would expect to see user personas. Different user personas have different workflows attached, and I think the RFC overindexes on library maintainers ("application developers" only appears once in the entire proposal).

I raise this concern since I believe there's a significant difference between the needs of a library maintainer and those of an application developer.

As an application developer, for example, I would be surprised to find out that the default resolver scheme is not maximum or, at the very least, rustc. My primary concern, as expressed before by @jonhoo, is to maximise the likelihood of running a version of my dependencies that doesn't contain known bugs or security vulnerabilities. Targeting a version of rustc that's not latest should be a deliberate choice, executed in full awareness of the consequences.

I expect library maintainers to be, on average, more sophisticated Rust users than application developers. The latter is also the largest group of users. Defaults should be picked by putting the needs of application developers at the forefront, rather than those of library maintainers. I realize this might be controversial, but I haven't seen this opinion stated clearly in the tens of comments I had a chance to read so far.

(moved from here to avoid interleaved conversations)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Different user personas have different workflows attached, and I think the RFC overindexes on library maintainers ("application developers" only appears once in the entire proposal).

Part of the reason that term only appears once is the focus hasn't been so much in those terms.

imo the "Latest MSRV" workflows are agnostic of app vs lib.

The "Extended MSRV" workflow acknowledges a lot of different user groups and I interpret most of those as talking about app developers. Only the last bullet item ("Library and tool maintainers catering to the above use cases") has libraries and still it is shared with app developers.

As an application developer, for example, I would be surprised to find out that the default resolver scheme is not maximum or, at the very least, rustc.

When I enumerated "Extended MSRV with latest dependencies", I only found it applicable to "Library and tool maintainers catering to the above use cases". Is that you? If not, that means there is a gap and I would love to better understand your situation, including

  • What type of application?
  • What is your MSRV policy?
  • Why do you have that MSRV policy?
  • How do you verify your MSRV?
  • Is the application meant to be installable with cargo install?
    • This is important because Cargo expects that Cargo.lock is compatible with your MSRV today and tells to users to pass --locked on MSRV failures. This also gets into the definition of the word "support" within MSRV on what all interactions with a project an MSRV applies to.

Copy link

@LukeMathWalker LukeMathWalker Jan 23, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Part of the reason that term only appears once is the focus hasn't been so much in those terms.

I understand that's not the framing being used. Take it as a feedback point: I think reframing the excellent work done on workflows around the different types of user intent would help focus the conversation and make the RFC more approachable/easier to read.
Even talking about "minimum supported Rust version" feels weird in application development. It's not about supporting a certain version of Rust, it's more about using a certain version of Rust (or not caring about it), under the (very common?) assumption that nobody but your own engineers will be building from source.

Is that you?

The vast majority of Rust gigs I have been at operated under this framework:

  • We are building an application that won't be distributed to users. It'll be running on a server/Cloud-platform of some kind.
  • We care about our applications' performance, correctness and security above everything else. Consequently, we should always be using the latest versions of our dependencies.
  • We don't have an MSRV. There might be a rust-toolchain.toml file in the repository to ensure that all developers are using the same version that's tested in CI, but the version is bumped up whenever someone remembers to do it or if there is a need (e.g. a dependency is using features from a newer version of Rust).

This feels closer to "Latest Rust as the MSRV", using the wording of the RFC.
For this kind of workflow, maximum if the resolver strategy that makes most sense to me. I'd go as far as saying that I'd love if rustup auto-updated the stable toolchain for me when it detects I need something newer.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Take it as a feedback point: I think reframing the excellent work done on workflows around the different types of user intent would help focus the conversation and make the RFC more approachable/easier to read.

I feel I'm missing something as I'm not seeing how this would help. Could you elaborate more on what you envisioning and why it would help? For me, I'm particularly looking to understand the value add it helps for the conversations vs the time sink it is to go and write up. The current 4 workflows are a collapsing down of what I was originally looking at doing which was overwhelming enough that the only alternative is to not do this RFC. So I'm coming from the perspective of "I've was thinking of something that vaguely sounds like what you're asking and it was untenable". What your asking might be something completely different and thats why I'm wanting to better understand. This might also be useful to do more directly. I have the standing invite that people can come to cargo office hours to discuss this and to reach out to me directly to schedule if those times don't work.

Even talking about "minimum supported Rust version" feels weird in application development. It's not about supporting a certain version of Rust, it's more about using a certain version of Rust (or not caring about it), under the (very common?) assumption that nobody but your own engineers will be building from source.

This is called out in the "Extended MSRV" workflow that sometimes they only support on version and they'll be setting it in rust-toolchain.toml instead of package.rust-version.

The vast majority of Rust gigs I have been at operated under this framework:

Thanks for the explanation!

For your use case, the RFC moves the message earlier (update, rather than the later check) and softens it (message, rather than error). There are still errors if the MSRV is unset and uses incompatible features but that will be reduced over time as the RFC encourages MSRV to be set in more cases via the "auto" value. For cases like yours where rust-toolchain.toml is used, we can't offer --update-rust-version (since that will only be for the manifest), we can at least suggest upgrading your toolchain instead.

While you might not be needing all of this hand holding, I hope the value of this hand holding is understandable within one of the prioritized workflow. My expectation is that this won't get in the way of following "Latest MSRV" but will provide a friendlier experience.

As the RFC points out, there are other application developers in the "Extended MSRV" workflow that those changes for friendliness make a big difference for making their experience workable.

I'd go as far as saying that I'd love if rustup auto-updated the stable toolchain for me when it detects I need something newer.

With toml_edit, this is doable! I recommend opening an issue for a command or flag to update a toolchain file.

Unsure how I feel about adding a future possibility of --update-rust-version modifying toolchain files. That would require breaking abstractions and require cargo to have a lot more context from the rustup invocation.


In solving this, we need to keep in mind how people are using Cargo and how to prioritize when needs of different workflows conflict.
We will then look at the potential designs within the context of this framework.

Some design criteria we can use for evaluating workflows:
- Cargo should not make major breaking changes
- Low barrier to entry
- The costs of “non-recommended” setups should focused on those that need them
- Encourage a standard of quality within the ecosystem, including
- Assumption that things will work as advertised (e.g. our success with MSRV)
- A pleasant experience (e.g. meaningful error messages)
- Secure
- Every feature has a cost and we should balance the cost against the value we expect
- Features can further constrain what can be done in the future due to backwards compatibility
- Features increase maintenance burden
- The larger the user-facing surface, the less likely users will find the feature they need and instead use the quickest shortcut
- Being transparent makes debugging easier, helps in evaluating risks (including security), and builds confidence in users
- Encourage progress and avoid stagnation
- Proactively upgrading means the total benefit to developers from investments made in Rust is higher
- Conversely, when most of the community is on old versions, it has a chilling effect on improving Rust
- This also means feedback can come more quickly, making it easier and cheaper to pivot with user needs
- Spreading the cost of upgrades over time makes forced-upgrades (e.g. for a security vulnerability) less of an emergency
- Our commitment to compatibility helps keep the cost of upgrade low
- When not competing with the above, we should do the right thing for the user rather than disrupt their flow to tell them what they should instead do

And keeping in mind
- The Rust project only supports the latest version
(e.g bug and security fixes)
and the burden for support for older versions is on the vendor providing the older Rust toolchain.
- Even keeping upgrade costs low, there is still a re-validation cost that mission critical applications must pay
- Dependencies in `Cargo.lock` are not expected to change from contributors using different versions of the Rust toolchain without an explicit action like changing `Cargo.toml` or running `cargo update`
- e.g. If the maintainer does `cargo add foo && git commit && git push`,
then a contributor doing `git pull && cargo check` should not have a different selection of dependencies, independent of their toolchain versions (which might mean the second user sees an error about an incompatible package).

Some implications:
- "Support" in MSRV implies the same quality and responsiveness to bug reports, regardless of Rust version
- MSRV applies to all interactions with a project within the maintainers control
(including as a registry dependency, `cargo install --locked`, as a git dependency, contributor experience; excluding transitive dependencies, rust-analyzer, etc),
unless documented otherwise like
- Some projects may document that enabling a feature will affect the MSRV (e.g. [moka](https://docs.rs/moka/0.12.1/moka/#minimum-supported-rust-versions))
- Some projects may have a higher MSRV for building the repo (e.g. `Cargo.lock` with newer dependencies, reliance on cargo features that get stripped on publish)
- We should focus the cost for maintaining support for older versions of Rust on the user of the old version and away from the maintainer or the other users of the library or tool
- Costs include lower developer productivity due to lack of access to features,
APIs that don't integrate with the latest features,
and slower build times due to pulling in extra code to make up for missing features
(e.g. clap dropping its dependency on
[is-terminal](https://crates.io/crates/is-terminal) in favor of
[`IsTerminal`](https://doc.rust-lang.org/std/io/trait.IsTerminal.html)
cut build time from [6s to 3s](https://github.com/rosetta-rs/argparse-rosetta-rs/commit/378cd2c30679afdf9b9843dbadea3e8951090809))

### Latest Rust with no MSRV
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@djc (moved to make it easier to follow the thread)

I definitely have some disagreement with the underlying principles, but one problem with the suggestions laid out in the initial post is that the RFC requires ploughing through a lot of text (rather than an attempt at concisely laying out the underlying design) to explain these principles themselves. I think there are roughly 4 quadrants of stakeholders:

* Users:
  
  1. Users who stick to stable or a recently stable version
  2. Users who stick to a more conservative MSRV

* Maintainers:
  3. Maintainers who don't want to care about the supported Rust version/have a liberal MSRV policy
  4. Maintainers who want to support a relatively conservative MSRV

The principles in the RFC don't seem to make as much of a distinction between users and maintainers, whereas I think this is an important distinction, as the groups have limited overlap, at least in the sense that most users are not maintainers. And, while there are many more users, we should highly value the maintainers because of the value they bring to the Rust ecosystem.

I think the changes here are intended mainly to improve the lives for those in group 2 -- users who are, for whatever reason, on an older version of Rust. To me it is important that the experience for group 4 (which I consider myself to be a part of) is not regressed, because I think a number of experienced maintainers of popular crates are in this category. Instead, you've prioritized this group (as far as I can tell) at priority 4, below all the other groups, which doesn't seem great.

In earlier discussions, I posited that this might be because there is a difference between maintaining low-level libraries (which have a larger audience, and thus more constraints on evolving compatibility) and higher-level libraries or applications. (I also think this matches with the actual people pushing back on this FCP.)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maintainers can find themselves in either "Extended MSRV" or "Extended published MSRV w/ latest development MSRV". For example, I do the former.

The main case I've seen for made for "Extended published MSRV w/ latest development MSRV" is that it enables maintainers to use the latest dependencies. However, the use of the latest dependencies is restricted to behavior of the dependencies and not their API. For me, the discussion of these benefits has been hand-wavy and it would help a lot for people to call out how they have been benefited and to describe why that anecdote is generally applicable enough for what the default is for the Rust community.

I also find myself jumping ahead and because there are points being made about costs related to that workflow, like being able to validate patches you are making to your dependencies. This gets into the question of what "support" in MSRV means.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For me, the discussion of these benefits has been hand-wavy and it would help a lot for people to call out how they have been benefited and to describe why that anecdote is generally applicable enough for what the default is for the Rust community.

To clarify, are you asking for an explanation of why I (and probably others) develop for an MSRV without using the toolchain associated with the MSRV in my development environment?

For me, I work on many different projects with different MSRVs but I've found the tooling good enough that I don't need to switch toolchains in my local development environment. I don't use rust-toolchain.toml files and mostly rely on CI jobs to check MSRV for me, and this seems like the path that requires the least effort to maintain to me.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The question is not "why do you use the latest toolchain?". The RFC actually encourages people to use the latest toolchain and some downsides for alternatives is that I believe it will push people to specifying a rust-toolchain.toml file.

I've seen a lot of value add in using the latest toolchain. What I do not see is enough value in using the latest, MSRV-incompatible dependencies for day to day development.

If I'm developing a library, I feel like most concerns about being "behind" seem negligible because I'm not a product on its own.

If I'm developing an application, then I should be using the same dependencies for testing and distribution.

  • If I'm distributing through cargo install, then --locked is expected to contain your MSRV dependencies
  • If I'm distributing using a verified toolchain which is why I have an MSRV, then I should be developing with the same dependencies as I distribute
  • That just leaves when I distribute using the latest toolchain but I want to allow others to build from source with older toolchains. If people are in this state, I'd want to better understand their situation (and not just hypotheticals we can come up with about it)


A user runs `cargo new` and starts development.

A maintainer may also want to avoid constraining their dependents, for a variety of reasons, and leave MSRV support as a gray area.

**Priority 1 because:**
- ✅ No MSRV is fine as pushing people to have an MSRV would lead to either
- an inaccurate reported MSRV from it going stale which would lower the quality of the ecosystem
- raise the barrier to entry by requiring more process for packages and pushing the cost of old Rust versions on people who don't care

**Pain points:**

The Rust toolchain does not provide a way to help users know new dependency versions are available, to support the users in staying up-to-date.
MSRV build errors from new dependency versions is one way to do it though not ideal as this disrupts the user.
Otherwise, they must actively run `rustup update` or follow Rust news.

For dependents, this makes it harder to know what versions are "safe" to use.

### Latest Rust as the MSRV

A maintainer regularly updates their MSRV to latest.
They can choose to provide a level of support for old MSRVs by reserving MSRV
changes to minor version bumps,
giving them room to backport fixes.
Due to the pain points listed below, the target audience for this workflow is likely small,
likely pushing them to not specify their MSRV.

**Priority 2 because:**
- ✅ Low barrier to maintaining a high quality of support for their MSRV
- ✅ Being willing to advertising an MSRV, even if latest, improves the information available to developers, increasing the quality of the ecosystem
- ✅ Costs for dealing with old Rust toolchains is shifted from the maintainer and the users on a supported toolchain to those on an unsupported toolchain
- ✅ By focusing new development on latest MSRV, this provides a carrot to encourage others to actively upgrading

**Pain points (in addition to the prior workflow):**

In addition to their toolchain version, the Rust toolchain does not help these users with keeping
their MSRV up-to-date.
They can use other tools like
[RenovateBot](https://github.com/rust-lang/cargo/blob/87eb374d499100bc945dc0e50ae5194ae539b964/.github/renovate.json5#L12-L24)
though that causes extra churn in the repo.

A package could offer a lower MSRV in an unofficial capacity or with a lower quality of support
but the requirement that dependents always pass `--ignore-rust-version` makes this disruptive.

### Extended MSRV

This could be people exclusively running one version or that support a range of versions.
So why are people on old versions?
- Not everyone is focused on Rust development and might only touch their Rust code once every couple of months,
making it a pain if they have to update every time.
- Think back to slow git index updates when you've stepped away and consider people who we'd be telling to run `rustup update` every time they touch Rust
- While a distribution provides rust to build other packages in the distribution,
users might assume that is a version to use, rather than getting Rust through `rustup`
- Re-validation costs for updating core parts of the image for an embedded Linux developers can be high, keeping them on older versions
- Updates can be slow within tightly controlled environments (airgaps, paperwork, etc)
- Qualifying Rust toolchains takes time and money, see [Ferrocene](https://ferrous-systems.com/ferrocene/)
- Built on or for systems that are no longer supported by rustc (e.g. old glibc, AndroidNDK, etc)
- Library and tool maintainers catering to the above use cases

The MSRV may extend back only a couple releases or to a year+ and
they may choose to update on an as-need basis or keep to a strict cadence.

Depending on the reason they are working with an old version,
they might be developing the project with it or they might be using the latest toolchain.
For some of these use cases, they might controlling their "MSRV" via `rust-toolchain.toml`, rather than `package.rust-version`, as its their only supported Rust version (e.g. an application with a vetted toolchain).

When multiple Rust versions are supported, like with library and tool maintainers,
they will need to verify at least their MSRV and latest.
Ideally, they also [verify their latest dependencies](https://doc.rust-lang.org/cargo/guide/continuous-integration.html#verifying-latest-dependencies)
though this is already a recommended practice when people follow the
[default choice to commit their lockfile](https://doc.rust-lang.org/cargo/faq.html#why-have-cargolock-in-version-control).
The way they verify dependencies is restricted as they can't rely on always updating via Dependabot/RenovateBot as a way to verify them.
Maintainers likely only need to do a compilation check for MSRV as their regular CI runs ensure that the behavior (which is usually independent of rust version) is correct for the MSRV-compatible dependencies.

**Priority 3 because:**
- ✅ Several use cases for this workflow have little alternative
- ✅ MSRV applies to all interactions to the project which also means that the level of "support" is consistent
- ❌ This implies stagnation and there are cases where people could more easily use newer toolchains, like Debian users, but that is less so the case for other users
- ❌ For library and tool maintainers, they are absorbing costs from these less common use cases
- They could shift these costs to those that need old versions by switching to the "Latest MSRV" workflow by allowing their users to backport fixes to prior MSRV releases

**Pain points:**

Maintaining a working `Cargo.lock` is frustrating, as demonstrated earlier.

When developing with the latest toolchain,
feedback is delayed until CI or an embedded image build process which can be frustrating
(using too-new dependencies, using too-new Cargo or Rust features, etc).

### Extended published MSRV w/ latest development MSRV

This is where the published package for a project claims an extended MSRV but interactions within the repo require the latest toolchain.
The requirement on the latest MSRV could come from the `Cargo.lock` containing dependencies with the latest MSRV or they could be using Cargo features that don't affect the published package.
In some cases, the advertised MSRV might be for a lower tier of support than what is supported for the latest version.
For instance, a project might intentionally skip testing against their MSRV because of known bugs that will fail the test suite.

In some cases, the MSRV-incompatible dependencies might be restricted to `dev-dependencies`.
Though local development can't be performed with the MSRV,
the fact that the tests are verifying (on a newer toolchain) that the build/normal dependencies work gives a good amount of confidence that they will work on the MSRV so long as they compile.

Compared to the above workflow, this is likely targeted at just library and tool maintainers as other use cases don't have access to the latest version or they are needing the repo to be compatible with their MSRV.

**Priority 4 because:**
- ❌ The MSRV has various carve outs, providing an inconsistent experience compared to other packages using other workflows and affecting the quality of the ecosystem
- For workspaces with bins, `cargo install --locked` is expected to work with the MSRV but won't
- If they use new Cargo features, then `[patch]`ing in a git source for the dependency won't work
- For contributors, they must be on an unspecified Rust toolchain version
- ❌ The caveats involved in this approach (see prior item) would lead to worse documentation which lowers the quality to users
- ❌ This still leads to stagnation despite being able to use the latest dependencies as they are limited in what they can use from them and they can't use features from the latest Rust toolchain
- ❌ These library and tool maintainers are absorbing costs from the less common use cases of their dependents
- They could shift these costs to those that need old versions by switching to the "Latest MSRV" workflow by allowing their users to backport fixes to prior MSRV releases

**Pain points:**

Like the prior workflow, when developing with the latest toolchain,
feedback is delayed until CI or an embedded image build process which can be frustrating
(using too-new dependencies, using too-new Cargo or Rust features, etc).

When `Cargo.lock` is resolved for latest dependencies, independent of MSRV,
verifying the MSRV becomes difficult as they must either juggle two lockfiles, keeping them in sync, or use the unstable `-Zminimal-versions`.
The two lockfile approach also has all of the problems shown earlier in writing the lockfile.

When only keeping MSRV-incompatible `dev-dependencies`,
one lockfile can be used but it can be difficult to edit the `Cargo.lock` to ensure you get new `dev-dependencies` without infecting other dependency types.

# Guide-level explanation
[guide-level-explanation]: #guide-level-explanation

We are introducing several new concepts
- A v3 resolver (`package.resolver`) that will prefer packages compatible with your `package.rust-version` over those that aren't
- If `package.rust-version` is unset, then your current Rust toolchain version will be used
- This resolver version will be the default for the next edition
- A `.cargo/config.toml` field will be added to disable this, e.g. for CI
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@djc (moved to better follow the threads)

I don't see how a config.toml field will make it easier to disable this only in CI?

All config fields are implicitly environment variables which you can set on the individual step or whole job within your CI.

In the reference section, we call out the name of this variable.

- Cargo will ensure users are aware their dependencies are behind the latest in a unobtrusive way
- `cargo add` will select version requirements that can be met by a dependency with a compatible version
- A new value for `package.rust-version`, `"tbd-name-representing-currently-running-rust-toolchain"`, which will advertise in your published package your current toolchain version as the minimum-supported Rust version
- `cargo new` will default to `package.rust-version = "tbd-name-representing-currently-running-rust-toolchain"`
- A deny-by-default lint will replace the build error from a package having an incompatible Rust version, allowing users to opt-in to overriding it

## Example documentation updates

### The `rust-version` field

*(update to [manifest documentation](https://doc.rust-lang.org/cargo/reference/manifest.html#the-rust-version-field))*

The `rust-version` field is an optional key that tells cargo what version of the
Rust language and compiler you support compiling your package with. If the currently
selected version of the Rust compiler is older than the stated version, cargo
will exit with an error, telling the user what version is required.
To support this, Cargo will prefer dependencies that are compatible with your `rust-version`.

```toml
[package]
# ...
rust-version = "1.56"
```

The Rust version can be a bare version number with two or three components; it
cannot include semver operators or pre-release identifiers. Compiler pre-release
identifiers such as -nightly will be ignored while checking the Rust version.
The `rust-version` must be equal to or newer than the version that first
introduced the configured `edition`.

The Rust version can also be `"tbd-name-representing-currently-running-rust-toolchain"`.
This will act the same as if it was set to the version of your Rust toolchain.
Your published manifest will have `"tbd-name-representing-currently-running-rust-toolchain"` replaced with the version of your Rust toolchain.

Setting the `rust-version` key in `[package]` will affect all targets/crates in
the package, including test suites, benchmarks, binaries, examples, etc.

*Note: The first version of Cargo that supports this field was released with Rust 1.56.0.
In older releases, the field will be ignored, and Cargo will display a warning.*

### Rust Version

*(update to [Dependency Resolution's Other Constraints documentation](https://doc.rust-lang.org/cargo/reference/resolver.html))*

When multiple versions of a dependency satisfy all version requirements,
cargo will prefer those with a compatible `package.rust-version` over those that
aren't compatible.
Some details may change over time though `cargo check && rustup update && cargo check` should not cause `Cargo.lock` to change.

##### `resolver.precedence`

*(update to [Configuration](https://doc.rust-lang.org/cargo/reference/config.html))*

* Type: string
* Default: "rust-version"
* Environment: `CARGO_RESOLVER_PRECEDENCE`

Controls how `Cargo.lock` gets updated on changes to `Cargo.toml` and with `cargo update`. This does not affect `cargo install`.

* `maximum`: prefer the highest compatible versions of dependencies
* `rust-version`: prefer dependencies where their `package.rust-version` is less than or equal to your `package.rust-version`

`rust-version` can be overridden with `--ignore-rust-version` which will fallback to `maximum`.

## Example workflows

We'll step through several scenarios to highlight the changes in the user experience.

### Latest Rust with MSRV

I'm learning Rust and wanting to write my first application.
The book suggested I install using `rustup`.

<details><summary>Expand for step through of this workflow</summary>

I've recently updated my toolchain
```console
$ rustup update
Downloading and install 1.92
```

At some point, I start a project:
```console
$ cargo new foo
$ cat foo/Cargo.toml
```

```toml
[package]
name = "foo"
version = "0.1.0"
edition = "2024"
rust-version = "tbd-name-representing-currently-running-rust-toolchain"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
```

```console
$ cargo add clap -F derive
Adding clap 5.10.30
```

*(note: this user would traditionally be a "Latest Rust" user but `package.rust-version` automatically them moved to "Latest Rust with MSRV" without extra validation effort or risk of their MSRV going stale)*

After some time, I get back to my project and decide to add completion support:
```console
$ cargo add clap_complete
Adding clap_complete 5.10.40
warning: clap_complete 5.11.0 exists but requires Rust 1.93 while you are running 1.92.
To use the clap_complete@5.11.0 with a compatible Rust version, run `rustup update && cargo add clap_complete@5.11.0`.
To force the use of clap_complete@5.11.0 independent of your toolchain, run `cargo add clap_complete@5.11.0`
```

Wanting to be on the latest version, I run
```console
$ rustup update
Downloading and install 1.94
$ cargo update
Updating clap v5.10.30 -> v5.11.0
Updating clap_complete v5.10.40 -> v5.11.0
```

**Alternate:** But what if I manually edited `Cargo.toml` instead of `cargo add`?
Here, we can shortcut some questions about version requirements because clap aligns on minor releases.
```toml
[package]
name = "foo"
version = "0.1.0"
edition = "2024"
rust-version = "tbd-name-representing-currently-running-rust-toolchain"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
clap = { version = "5.10.30", features = ["derive"] }
clap_complete = "5.10" # <-- new
```

And away I go:
```console
$ cargo check
Warning: adding clap_complete@5.10.40 because 5.11.0 requires Rust 1.93 while you are running 1.92.
To use the clap_complete@5.11.0 with a compatible Rust version, run `rustup update && cargo update`.
To force the use of clap_complete@5.11.0 independent of your toolchain, run `cargo update --ignore-rust-version`
```

But I am in a hurry and don't want to disrupt my flow.
`clap_complete@5.10.40` is likely fine.
I am running `clap@5.10.30` and that has been working for me.
I might even run [`cargo deny`](https://crates.io/crates/cargo-deny) to see if there are known vulnerabilities.
So I continue development.

Later I run:
```console
$ cargo update
Name Current Latest Note
============= ======= ====== ==================
clap 5.10.30 5.11.0 requires Rust 1.93
clap_complete 5.10.40 5.11.0 requires Rust 1.93
note: To use the latest depednencies, run `rustup update && cargo update`.
To force the use of the latest dependencies, independent of your toolchain, run `cargo update --ignore-rust-version`
$ rustup update
Downloading and install 1.94
$ cargo update
Updating clap v5.10.30 -> v5.11.0
Updating clap_complete v5.10.40 -> v5.11.0
```

At this point, I want to publish
```console
$ cargo publish
... crates.io error about missing fields
$ $EDITOR `Cargo.toml`
$ cargo publish
Published foo 0.1.0
```

If I look on crates.io, the new 0.1.0 version shows up with a rust-version of 1.94
without me having to manual update the field and
relying on the `cargo publish`s verify step to verify the correctness of that MSRV.

</details>

### Extended "MSRV" with an application

I am developing an application using a certified toolchain.
I specify this toolchain using a `rust-toolchain.toml` file.

Rust 1.94 is the latest but my certified toolchain is 1.92.

<details><summary>Expand for step through of this workflow</summary>

At some point, I start a project:
```console
$ cargo new foo
$ cat foo/Cargo.toml
```

```toml
[package]
name = "foo"
version = "0.1.0"
edition = "2024"
rust-version = "tbd-name-representing-currently-running-rust-toolchain"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
```

```console
$ cargo add clap -F derive
Adding clap 5.10.30
warning: clap 5.11.0 exists but requires Rust 1.93 while you are running 1.92.
To use the clap@5.11.0 with a compatible Rust version, run `rustup update && cargo add clap@5.10.0`.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Running rustup update won't help if there's a toolchain file being used as this case mentions. It may be better not to mention rustup, since it's also possible that we're not using a rustup distribution.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: this is more for the implementation, than the RFC.

We can detect rustup is being used and do so today in many cases to specialize the error messages. As of today, I don't think we can detect the use of a rust-toolchain.toml file.

For myself, I think this message is worth it enough for when there isn't a toolchain file but we can figure those things out during the implementation.

To force the use of clap_complete@5.11.0 independent of your toolchain, run `cargo add clap@5.10.0`
```

At this point, I have a couple of options
1. I check and clap advertises that they "support" Rust 1.92 by cherry-picking fixes into 5.10 and I feel comfortable with that
2. I check `cargo deny` and don't see any vulnerabilities and that is good enough for me, knowing that the majority of my users are likely on newer versions
3. I decide that clap doesn't align with my interests and use something else

Assuming (1) or (2) applies, I ignore the warning and move on.

</details>

### Extended MSRV with an application targeting multiple Rust versions

*(this is a re-imagining of the Motivation's example)*

I'm building an application that is deployed to multiple embedded Linux targets.
Each target's image builder uses a different Rust toolchain version to avoid re-validating the image.

<details><summary>Expand for step through of this workflow</summary>

I've recently updated my toolchain
```console
$ rustup update
Downloading and install 1.94
```

At some point, I start a project:
```console
$ cargo new foo
$ cat foo/Cargo.toml
```

```toml
[package]
name = "foo"
version = "0.1.0"
edition = "2024"
rust-version = "tbd-name-representing-currently-running-rust-toolchain"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
```

```console
$ cargo add clap -F derive
Adding clap 5.11.0
```

I send this to my image builder and I get this failure for one of my embedded targets:
```console
$ cargo build
error: clap 5.11.0 requires Rust 1.93.0 while you are running 1.92.0

note: downgrade to 5.10.30 for a version compatible with Rust 1.92.0
note: set `package.rust-version = "1.92.0"` to ensure compatible versions are selected in the future
note: lint `cargo::incompatible-msrv` is denied by default

```

I make the prescribed changes:
```toml
[package]
name = "foo"
version = "0.1.0"
edition = "2024"
rust-version = "1.92" # <-- was "tbd-name-representing-currently-running-rust-toolchain" before I edited it

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
clap = { version = "5.10.30", features = ["derive"] } # <-- downgraded
```

And my image build works!

After some time, I run:
```console
$ cargo update
Name Current Latest Note
============= ======= ====== ==================
clap 5.10.30 5.11.0 requires Rust 1.93
clap_complete 5.10.40 5.11.0 requires Rust 1.93
note: To use the latest depednencies, run `rustup update && cargo update`.
To force the use of the latest dependencies, independent of your toolchain, run `cargo update --ignore-rust-version`
```

We've EOLed the last embedded target that supported 1.92 and so we can update our `package.rust-version`,
so we can update it and our dependencies:
```console
$ cargo update --update-rust-version
Updating clap 5.10.30 to 5.11.0
Updating foo's rust-version from 1.92 to 1.93
```

</details>

### Extended MSRV for a Library

I'm developing a new library and am willing to take on some costs for supporting people on older toolchains.

<details><summary>Expand for step through of this workflow</summary>

I've recently updated my toolchain
```console
$ rustup update
Downloading and install 1.94
```

At some point, I start a project:
```console
$ cargo new foo --lib
```

I've decided on an "N-2" MSRV policy:
```toml
[package]
name = "foo"
version = "0.1.0"
edition = "2024"
rust-version = "1.92" # <-- was "tbd-name-representing-currently-running-rust-toolchain" before I edited it

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
```

```console
$ cargo add clap -F derive
Adding clap 5.10.30
warning: clap 5.11.0 exists but requires Rust 1.93 while `foo` has `package.rust-version = "1.92"`
To use clap@5.11.0 with a compatible package.rust-version, run `cargo add clap@5.11.0 --update-rust-version`
To force the use of clap@5.11.0 independent of your toolchain, run `cargo add clap@5.11.0`
```

At this point, I have a couple of options
1. I check and clap advertises that they "support" Rust 1.92 by cherry-picking fixes into 5.10 and I feel comfortable with that
2. I check `cargo deny` and don't see any vulnerabilities and that is good enough for me, knowing that the majority of my users are likely on newer versions
3. I decide that clap doesn't align with my interests and use something else

Assuming (1) or (2) applies, I ignore the warning and move on.

After some time, I run:
```console
$ cargo update
Name Current Latest Note
============= ======= ====== ==================
clap 5.10.30 5.11.0 requires Rust 1.93
clap_complete 5.10.40 5.11.0 requires Rust 1.93
note: To use the latest depednencies, run `rustup update && cargo update`.
To force the use of the latest dependencies, independent of your toolchain, run `cargo update --ignore-rust-version`
```

At this point, 1.95 is out, so I'm fine updating my MSRV and I run:
```console
$ cargo update --update-rust-version
Updating clap 5.10.30 to 5.11.0
Updating foo's rust-version from 1.92 to 1.93
```

Instead, if a newer clap version was out needing 1.94 or 1.95, I would instead edit `Cargo.toml` myself.

</details>

# Reference-level explanation
[reference-level-explanation]: #reference-level-explanation

We expect these changes to be independent enough and beneficial on their own that they can be stabilized as each is completed.

## Cargo Resolver

We will be adding a v3 resolver, specified through `workspace.resolver` / `package.resolver`.
This will become default with the next Edition.

When `resolver = "3"` is set, Cargo's resolver will change to *prefer* MSRV compatible versions over
incompatible versions when resolving new dependencies, except for `cargo install`.
Initially, dependencies without `package.rust-version` will be preferred over
MSRV-incompatible packages but less than those that are compatible.
Comment on lines +789 to +790

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This basically means that if a crate author publishes a version with package.rust-version included, they are required to include that field in every subsequent crate version they publish. And if they don't, rust tooling will pretend that the newer published versions simply don't exist

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Regardless of what answer we pick, it will have negative results.

This is why the future possibilities are important to help us have heuristics for improving the situation when no MSRV is present. However, I suspect that fixing this isn't so important that it has to block the RFC. Most of them likely don't even need an RFC on their own but an MCP from the relevant teams.

The exact details for how preferences are determined may change over time,
particularly when no MSRV is specified,
but this shouldn't affect existing `Cargo.lock` files since the currently
resolved dependencies always get preference.

This can be overridden with `--ignore-rust-version` and config's `resolver.precedence`.

Implications
- If you use `cargo update --precise <msrv-incompatible-ver>`, it will work
- If you use `--ignore-rust-version` once, you don't need to specify it again to keep those dependencies though you might need it again on the next edit of `Cargo.toml` or `cargo update` run
- If a dependency doesn't specify `package.rust-version` but its transitive dependencies specify an incompatible `package.rust-version`,
we won't backtrack to older versions of the dependency to find one with a MSRV-compatible transitive dependency.
- A package with multiple MSRVs, depending on the features selected, can still do this as version requirements can still require versions newer than the MSRV and `Cargo.lock` can depend on those as well.

As there is no `workspace.rust-version`,
the resolver will pick the lowest version among workspace members.
This will be less optimal for workspaces with multiple MSRVs and dependencies unique to the higher-MSRV packages.
Users can workaround this by raising the version requirement or using `cargo update --precise`.

When `rust-version` is unset,
we'll fallback to `rustc --version` if its not a pre-release.
This is primarily targeted at helping users with a
[`rust-toolchain.toml` file](https://rust-lang.github.io/rustup/overrides.html#the-toolchain-file)
(to reduce duplication)
though this would also help users who happen to be on an old rustc, for whatever reason.
As this is just a preference for resolving dependencies, rather than prescriptive,
this shouldn't cause churn of the `Cargo.lock` file.
We already call `rustc` for feature resolution, so hopefully this won't have a performance impact.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is not clear to me what “feature resolution” refers to.


## Cargo config

We'll add a `resolver.precedence ` field to `.cargo/config.toml` which will control the package version prioritization policy.

```toml
[build]
resolver.precedence = "rust-version" # Default with `v3`
```

with potential values being:
- `maximum`: behavior today (default for v1 and v2 resolvers)
- Needed for [verifying latest dependencies](https://doc.rust-lang.org/nightly/cargo/guide/continuous-integration.html#verifying-latest-dependencies)
- `minimum` (unstable): `-Zminimal-versions`
- As this is just precedence, `-Zdirect-minimal-versions` doesn't fit into this
- `rust-version`: what is defined in the package (default for v3 resolver)
- `rust-version=` (future possibility)
- `package`: long form of `rust-version`
- `rustc`: the current running version
- Needed for "separate development / publish MSRV" workflow
- `<x>[.<y>[.<z>]]`: manually override the version used

If a `rust-version` value is used, we'd switch to `maximum` when `--ignore-rust-version` is set.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The wording on here is confusing to me. Does this mean that explicitly setting precedence = maximum is the same as passing --ignore-rust-version?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is more intended as a rational for why we aren't proposing every command get a --ignore-rust-version flag.

I've moved this down into the Alternatives and clarified the wording in
cf4651b and 13fcb24


## `cargo build`

The MSRV-compatibility build check will be demoted from an error to a `deny`-by-default workspace
[diagnostic](https://github.com/rust-lang/cargo/issues/12235),
allowing users to intentionally use dependencies on an unsupported (or less supported) version of Rust
without requiring `--ignore-rust-version` on every invocation.

Ideally, we present all of the MSRV issues upfront to be resolved together.
At minimum, we should present a top-down message, rather than bottom up.

If `package.rust-version` is unset or `"tbd-name-representing-currently-running-rust-toolchain"`, the diagnostic should suggest setting it
to help raise awareness of `package.rust-version` being able to reduce future
resolution errors.
This would benefit from knowing the oldest MSRV.

## `cargo update`

`cargo update` will inform users when an MSRV or semver incompatible version is available.
`cargo update --dry-run` will also report this information so that users can check on the status of this at any time.

Users may pass
- `--ignore-rust-version` to pick the latest dependencies, ignoring all `rust-version` fields (your own and from dependencies)
- `--update-rust-version` to pick the `rustc --version`-compatible dependencies, updating your `package.rust-version` if needed to match the highest of your dependencies
- `<pkgname> --precise <version>` to pick a specific version, independent of the `rust-version` field

We expect the notice to inform users of these options for allowing them to upgrade.

Those flags will also be added to `cargo generate-lockfile`

## Syncing `Cargo.toml` to `Cargo.lock` on any Cargo command

In addition to the `cargo update` output to report when things are held back (both MSRV and semver),
we will try having dependency resolves highlight newly selected dependency versions that were held back due to MSRV or semver.
Whether we do this and how much will be subject to factors like noisy output, performance, etc.

Some approaches we can take for doing this include:

After resolving, we can do a depth-first diff of the trees, stopping and reporting on the first different node.
This would let us report on any command that changes the way the tree is resolved
(from explicit changes with `cargo update` to `cargo build` syncing `Cargo.toml` changes to `Cargo.lock`).
We'd likely want to limit the output to only the sub-tree that changed.
If there wasn't previously a `Cargo.lock`, this would mean everything.

We could either always do the second resolve or only do the second resolve if the resolver changed anything,
whichever is faster.

Its unknown whether making the inputs available for multiple resolves would have a performance impact.

While a no-change resolve is fast, if this negatively impacts it enough, we
could explore hashing the resolve inputs and storing that in the lockfile,
allowing us to detect if the inputs have changed and only resolving then.


## `cargo add`

`cargo add <pkg>` (no version) will pick a version requirement that is low
enough so that when it resolves, it can pick a dependency that is
MSRV-compatible.
`cargo add` will warn when it does this.

Users may pass
- `--ignore-rust-version` to pick the latest dependencies, ignoring all `rust-version` fields (your own and from dependencies)
- `--update-rust-version` to pick the `rustc --version`-compatible dependencies, updating your `package.rust-version` if needed to match the highest of your dependencies

## `cargo publish`

`package.rust-version` will gain support for an `"tbd-name-representing-currently-running-rust-toolchain"` value, in addition to partial versions.
On `cargo publish` / `cargo package`, the generated `*.crate`s `Cargo.toml` will have `"tbd-name-representing-currently-running-rust-toolchain"` replaced with `rustc --version`.
If `rustc --version` is a pre-release, publish will fail.

`cargo new` will include `package.rust-version = "tbd-name-representing-currently-running-rust-toolchain"`.

# Drawbacks
[drawbacks]: #drawbacks

Users upgrading to the next Edition (or changing to `resolver = '3"`), will have to manually update their CI to test the latest dependencies with `CARGO_RESOLVER_PRECEDENCE=maximum`.

Workspaces have no `edition`, so its easy for users to not realize they need to set `resolver = "3"` or to update their `resolver = "2"` to `"3"`
(Cargo only warns on [virtual manifests without an explicit `workspace.resolver`](https://github.com/rust-lang/cargo/pull/10910)).

While we hope this will give maintainers more freedom to upgrade their MSRV,
this could instead further entrench rust-version stagnation in the ecosystem.

For projects with larger MSRVs than their dependencies,
this introduces another form of drift from the latest dependencies
(in addition to [lockfiles](https://doc.rust-lang.org/cargo/faq.html#why-have-cargolock-in-version-control)).
However, we already recommend people
[verify their latest dependencies](https://doc.rust-lang.org/nightly/cargo/guide/continuous-integration.html#verifying-latest-dependencies),
so the only scenario this further degrades is when lockfiles are verified by always updating to the latest, like with RenovateBot,
and only in the sense that the user needs to know to explicitly take action to add another verification job to CI.

# Rationale and alternatives
[rationale-and-alternatives]: #rationale-and-alternatives

Misc alternatives
- Dependencies with unspecified `package.rust-version`: we could mark these as always-compatible or always-incompatible; there really isn't a right answer here.
- The resolver doesn't support backtracking as that is extra complexity that we can always adopt later as we've reserved the right to make adjustments to what `cargo generate-lockfile` will produce over time.
- `CARGO_RESOLVER_PRECEDENCE` is used, rather than a CLI option (e.g. ensuring every command has `--ignore-rust-version` or a `--rust-version <x.y.z>`)
- This is unlikely to be used in one-off cases but across whole interactions which is better suited for config / env variables, rather than CLI options
- Minimize CLI clutter
- `CARGO_RESOLVER_PRECEDENCE=rust-version` implies maximal resolution among MSRV-compatible dependencies. Generally MSRV doesn't decrease over versions, so minimal resolution will likely pick packages with compatible rust-versions.
- `cargo add` helps by selecting rust-version-compatible minimum bounds
- This bypasses a lot of complexity either from exploding the number of states we support or giving users control over the fallback by making the field an array of strategies.
- Instead of `resolver = "3"`, we could just change the default for everyone
- The number of maintainers verifying latest dependencies is likely
relatively low and they are more likely to be "in the know",
making them less likely to be negatively affected by this.
Therefore, we could probably get away with treating this as a minor incompatibility
- Either way, the concern is to ensure that the change receives attention.
We wouldn't want this to be like sparse registries where a setting exists and we change the default and people hardly notice (besides any improvements)
- `cargo build` will treat incompatible MSRVs as a workspace-level lint, rather than a package level lint, to avoid the complexity of mapping the dependency to a workspace-member to select `[lint]` tables to respect and then dealing with unifying conflicting levels in between `[lint]` tables among members.
- `--ignore-rust-version` picks absolutely the latest dependencies to support both users on latest rustc and users wanting "unsupported" dependencies, at the cost of users not on the latest rustc but still wanting latest more up-to-date dependencies than their MSRV allows
- Compilation commands (e.g. `cargo check`) will take on two meanings for `--ignore-rust-version`, (1) `allow` the workspace diagnostic and (2) resolve changed dependencies to latest when syncing `Cargo.toml` to `Cargo.lock`.
- This expansion of scope is for consistency
- Being a flag to turn the `deny` into an `allow` is a high friction workflow that we expect users to not be too negatively impacted by this expansion.
- With the resolver config and the configurable lint, we also expect the flag on compilation commands to be diminished in value. Maybe in the future we could even deprecate it and/or hide it.
- `--update-rust-version` picks `rustc --version`-compatible dependencies so users can easily walk the treadmill of updating their dependencies / MSRV , no matter their `rustc` version.
- There is little reason to select an MSRV higher than their Rust toolchain
- We should still be warning the user that new dependencies are available if they upgrade their Rust toolchain
- This comes at the cost of inconsistency with `--ignore-rust-version`.
- Nightly `cargo publish` with `"tbd-name-representing-currently-running-rust-toolchain"` fails because there isn't a good value to use and this gives us flexibility to change it later (e.g. just leaving the `rust-version` as unset).

## Ensuring the registry Index has `rust-version` without affecting quality

The user experience for this is based on the extent and quality of the data.
Ensuring we have `package.rust-version` populated more often (while maintaining
quality of that data) is an important problem but does not have to be solved to
get value out of this RFC and can be handled separately.

We chose an opt-in for populating `package.rust-version` based on `rustc --version` (`"tbd-name-representing-currently-running-rust-toolchain"`).
This will encourage a baseline of quality as users are developing with that version and `cargo publish` will do a verification step, by default.
This will help seed the Index with more `package.rust-version` data for the resolver to work with.
The downside is that the `package.rust-version` will likely be higher than it absolutely needs.
However, considering our definition of "support" and that the user isn't bothering to set an MSRV themself,
aggressively updating is likely fine in this case, especially since we'll let dependents override the build failure for MSRV-incompatible packages.

Some alternative solutions include:

When missing, `cargo publish` could inject `package.rust-version` using the version of rustc used during publish.
**However**, this will err on the side of a higher MSRV than necessary and the only way to
work around it is to set `CARGO_RESOLVER_PRECEDENCE=maximum` which will then lose
all other protections.
As we said, this is likely fine but then there will be no way to opt-out for the subset of maintainers who want to keep their support definition vague.
As things evolve, we could re-evaluate making `"tbd-name-representing-currently-running-rust-toolchain"` the default.

~~We could encourage people to set their MSRV by having `cargo new` default `package.rust-version`.~~
**However**, if people aren't committed to verifying that was implicitly set,
it is likely to go stale and will claim an MSRV much older than what is used in practice.
If we had the hard-error resolver mode and
[clippy warning people when using API items stabilized after their MSRV](https://github.com/rust-lang/rust-clippy/issues/6324),
this will at least annoy people into either being somewhat compatible or removing the field.

~~When missing, `cargo publish` could inject `package.rust-version` inferred from
`package.edition` and/or other `Cargo.toml` fields.~~
**However**, this will err on the side of too low of an MSRV.
These fields have an incomplete picture.
While this helps ensure there is more data for the MSRV-aware resolver,
future analysis wouldn't be able to distinguish between inferred and explicit `package.rust-version`s.
We'd also need an explicit opt-out for those who intentionally don't want one set.

Alternatively, `cargo publish` / the registry could add new fields to the Index
to represent an inferred MSRV, the published version, etc
so it can inform our decisions without losing the intent of the publisher.

We could help people keep their MSRV up to date, by letting them specify a policy
(e.g. `rust-version-policy = "stable - 2"` or `rust-version-policy = "stable"`);
then, every time the user runs `cargo update`,
we could automatically update their `rust-version` field as well.
This would also be an alternative to `--update-rust-version` that can be further explored in the future if desired.
There are aspects of this that need to be worked out before going down this route
- Without gating this behind a flag, this will push people away from bumping their MSRV only on minor version bumps.
- Tying this to `cargo update` encourages other side effects by default (`--workspace` flag would be needed to do no other update) which pushes people to a more casual approach to MSRV updating, even if we have a flag
- We need to figure out what policies are appropriate and what syntax to use for them
- While a continuous sliding window (`N-M`) is most commonly used today,
it is unclear if that is the right policy to bake in compared to others like periodic updates (`*/M` in cron syntax) to be helping the "Extended MSRV" users along with everyone else.
- Is `stable` clear enough to mean "current version a time of `cargo update` with ratcheting semantics"? What name can work best?

When there still isn't an MSRV set, the resolver could
- Assume the MSRV of the next published package with an MSRV set
- Sort no-MSRV versions by minimal versions, the lower the version the more likely it is to be compatible
- This runs into quality issues with version requirements that are likely too low for what the package actually needs
- For dependencies that never set their MSRV, this effectively switches us from maximal versions to minimal versions.

## Configuring the resolver mode on the command-line or `Cargo.toml`

The Cargo team is very interested in [moving project-specific config to manifests](https://github.com/rust-lang/cargo/issues/12738).
However, there is a lot more to define for us to get there. Some routes that need further exploration include:
- If its a CLI flag, then its transient, and its unclear which modes should be transient now and in the future
- We could make it sticky by tracking this in `Cargo.lock` but that becomes less obvious what resolver mode you are in and how to change
- We could put this in `Cargo.toml` but that implies it unconditionally applies to everything
- But we want `cargo install` to use the latest dependencies so people get bug/security fixes
- This gets in the way of "Extended published MSRV w/ latest development MSRV" being able to change it in CI to verify MSRV and "Extended MSRV" being able to change it in CI to verify latest dependencies

By relying on config we can have a stabilized solution sooner and we can work out more of the details as we better understand the relevant problems.

## Add `workspace.rust-version`

Instead of using the lowest MSRV among workspace members, we could add `workspace.rust-version`.

This opens its own set of questions
- Do packages implicitly inherit this?
- What are the semantics if its unset?
- Would it be confusing to have this be set in mixed-MSRV workspaces? Would blocking it be incompatible with the semantics when unset?
- In mixed-MSRV workspaces, does it need to be the highest or lowest MSRV of your packages?
- For the resolver, it would need to be the lowest but there might be other use cases where it needs to be the highest

The proposed solution does not block us from later going down this road but
allows us to move forward without having to figure out all of these details.

## Resolver behavior

Effects of current solution on workflows (including non-resolver behavior):
1. Latest Rust with no MSRV
-`cargo new` setting `package.rust-version = "tbd-name-representing-currently-running-rust-toolchain"` moves most users to "Latest Rust as the MSRV" with no extra maintenance cost
- ✅ Dealing with incompatible dependencies will have a friendlier face because the hard build error after changing dependencies is changed to a notification during update suggesting they upgrade to get the new dependency because we fallback to `rustc --version` when `package.rust-version` is unset (as a side effect of us capturing `rust-toolchain.toml`)
2. Latest Rust as the MSRV
- ✅ Packages can more easily keep their MSRV up-to-date with
- `package.rust-version = "tbd-name-representing-currently-running-rust-toolchain"` (no policy around when it is changed) though this is dependent on your Rust toolchain being up-to-date (see "Latest Rust with no MSRV" for more)
- `cargo update --update-rust-version` (e.g. when updating minor version) though this is dependent on what you dependencies are using for an MSRV
- ✅ Packages can more easily offer unofficial support for an MSRV due to shifting the building with MSRV-incompatible dependencies from an error to a `deny` diagnostic
3. Extended MSRV
-`Cargo.lock` will Just Work
4. Extended published MSRV w/ latest development MSRV
- ❌ Maintainers will have to opt-in to latest dependencies, in a `.cargo/config.toml`
- ✅ Verifying MSRV will no longer require juggling `Cargo.lock` files or using unstable features

A short term benefit (hence why this is separate) is that an MSRV-aware resolver by default is that we can use it as a polyfill for
[`cfg(version)`](https://dev-doc.rust-lang.org/stable/unstable-book/language-features/cfg-version.html)
(which will likely need a lot of work in cargo after we finish stabilizing it for rustc).
A polyfill package can exist that has multiple maintained semver-compatible versions with different MSRVs with the older ones leveraging external libraries while the newer ones leverage the standard library.

### Make this opt-in rather than opt-out

Instead of adding `resolver = "3"`, we could keep the default resolver the same as today but allow opt-in to MSRV-aware resolver via `CARGO_RESOLVER_PRECEDENCE=rust-version`.
- When building with old Rust versions, error messages could suggest re-resolving with `CARGO_RESOLVER_PRECEDENCE=rust-version`.
The next corrective step (and suggestion from cargo) depends on what the user is doing and could be either
- `git checkout main -- Cargo.lock && cargo check`
- `cargo generate-lockfile`
- We'd drop from this proposal `cargo update [--ignore-rust-version|--update-rust-version]` as they don't make sense with this new default

This has no impact on the other proposals (`cargo add` picking compatible versions, `package.rust-version = "tbd-name-representing-currently-running-rust-toolchain"`, `cargo build` error to diagnostic).

Effects on workflows (including non-resolver behavior):
1. Latest Rust with no MSRV
-`cargo new` setting `package.rust-version = "tbd-name-representing-currently-running-rust-toolchain"` moves most users to "Latest Rust as the MSRV" with no extra maintenance cost
- 🟰 ~~Dealing with incompatible dependencies will have a friendlier face because the hard build error after changing dependencies is changed to a notification during update suggesting they upgrade to get the new dependency because we fallback to `rustc --version` when `package.rust-version` is unset (as a side effect of us capturing `rust-toolchain.toml`)~~
2. Latest Rust as the MSRV
- ✅ Packages can more easily keep their MSRV up-to-date with
- `package.rust-version = "tbd-name-representing-currently-running-rust-toolchain"` (no policy around when it is changed) though this is dependent on your Rust toolchain being up-to-date (see "Latest Rust with no MSRV" for more)
- ~~`cargo update --update-rust-version` (e.g. when updating minor version) though this is dependent on what you dependencies are using for an MSRV~~
- ❌ Without `cargo update --update-rust-version`, `"tbd-name-representing-currently-running-rust-toolchain"` will be more of a default path, leading to more maintainers updating their MSRV more aggressively and waiting until minors
- ✅ Packages can more easily offer unofficial support for an MSRV due to shifting the building with MSRV-incompatible dependencies from an error to a `deny` diagnostic
3. Extended MSRV
- ✅ Users will be able to opt-in to MSRV-compatible dependencies, in a `.cargo/config.toml`
- ❌ Users will be frustrated that the tool knew what they wanted and didn't do it
4. Extended published MSRV w/ latest development MSRV
- 🟰 ~~Maintainers will have to opt-in to latest dependencies, in a `.cargo/config.toml`~~
- ✅ Verifying MSRV will no longer require juggling `Cargo.lock` files or using unstable features

### Make `CARGO_RESOLVER_PRECEDENCE=rustc` the default

Instead of `resolver = "3"` changing the behavior to `CARGO_RESOLVER_PRECEDENCE=rust-version`,
it is changed to `CARGO_RESOLVER_PRECEDENCE=rustc` where the resolver selects packages compatible with current toolchain,
matching the `cargo build` incompatible dependency error.
- We would still support `CARGO_RESOLVER_PRECEDENCE=rust-version` to help "Extended MSRV" users
- We'd drop from this proposal `cargo update [--ignore-rust-version|--update-rust-version]` as they don't make sense with this new default

This has no impact on the other proposals (`cargo add` picking compatible versions, `package.rust-version = "tbd-name-representing-currently-running-rust-toolchain"`, `cargo build` error to diagnostic).

This is an auto-adapting variant where
- If they are on the latest toolchain, they get the current behavior
- If their toolchain matches their MSRV, they get an MSRV-aware resolver

Effects on workflows (including non-resolver behavior):
1. Latest Rust with no MSRV
-`cargo new` setting `package.rust-version = "tbd-name-representing-currently-running-rust-toolchain"` moves most users to "Latest Rust as the MSRV" with no extra maintenance cost
- ✅ Dealing with incompatible dependencies will have a friendlier face because the hard build error after changing dependencies is changed to a notification during update suggesting they upgrade to get the new dependency because we fallback to `rustc --version` when `package.rust-version` is unset (as a side effect of us capturing `rust-toolchain.toml`)
2. Latest Rust as the MSRV
- ✅ Packages can more easily keep their MSRV up-to-date with
- `package.rust-version = "tbd-name-representing-currently-running-rust-toolchain"` (no policy around when it is changed) though this is dependent on your Rust toolchain being up-to-date (see "Latest Rust with no MSRV" for more)
- ~~`cargo update --update-rust-version` (e.g. when updating minor version) though this is dependent on what you dependencies are using for an MSRV~~
- ❌ Without `cargo update --update-rust-version`, `"tbd-name-representing-currently-running-rust-toolchain"` will be more of a default path, leading to more maintainers updating their MSRV more aggressively and waiting until minors
- ✅ Packages can more easily offer unofficial support for an MSRV due to shifting the building with MSRV-incompatible dependencies from an error to a `deny` diagnostic
3. Extended MSRV
- ✅ Users will be able to opt-in to MSRV-compatible dependencies, in a `.cargo/config.toml`
- ❌ Users will be frustrated that the tool knew what they wanted and didn't do it
- ❌ This may encourage maintainers to develop using their MSRV, reducing the quality of their experience (not getting latest lints, not getting latest cargo features like "wait for publish", etc)
4. Extended published MSRV w/ latest development MSRV
- ❌ Maintainers will have to opt-in to ensure they get the latest dependencies in a `.cargo/config.toml`
- ✅ Verifying MSRV will no longer require juggling `Cargo.lock` files or using unstable features
Comment on lines +1130 to +1132
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(This is a comment under the listed alternative where resolver.precedence=rustc is the default.)

Workflow 4 is more or less the one we use for Tokio. You can build Tokio itself with the MSRV, but you can't use it to run our test suite and so on. Because of that, I'm surprised to see you list an ❌ here for the maintainer experience, because resolver.precedence=rustc is the default I would need to avoid introducing a .cargo/config.toml file

A few different cases:

  • ✅ Run our test suite. You want the latest version of all dependencies. You run the test suite under the latest rustc.
  • ✅ Verify that Tokio works under the MSRV. We want to pick the latest versions of dependencies compatible with the MSRV, and we run the CI step using the appropriate old compiler.
  • ✅ Backporting to an LTS. When we branch an LTS release, we fix the version of rustc to be whatever is the latest at the time. Several months later when I need to make a backport, I go back to the LTS branch, and the test-suite-rustc is now old. There may be test-only dependencies that don't support it. With resolver.precedence=rustc, running the test suite will just work anyway.

It is argued earlier that Tokio doesn't fit into workflow-4, but it's not clear to me what the important difference is. Have I missed some difference which has the effect that resolver.precedence=rustc is an ✅ on maintainer experience for Tokio, but ❌ on maintainer experience for "pure" workflow-4 users?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because of that, I'm surprised to see you list an ❌ here for the maintainer experience, because resolver.precedence=rustc is the default I would need to avoid introducing a .cargo/config.toml file

That ❌ is trying to cover the fact that a contributor could be behind on rustc and add or update a dependency and you wouldn't have gotten the latest dependencies. I think the divergence comes in on whether you are following our new default policy of always committing Cargo.lock. If you don't commit Cargo.lock, it is more neutral.

It is argued earlier #3537 (comment), but it's not clear to me what the important difference is. Have I missed some difference which has the effect that resolver.precedence=rustc is an ✅ on maintainer experience for Tokio, but ❌ on maintainer experience for "pure" workflow-4 users?

I feel I'm missing something. Tokio didn't come up in terms of what workflow they belonged to but where they fell when prioritizing user personas and were lumped in with other projects (serde, libc, etc).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That ❌ is trying to cover the fact that a contributor could be behind on rustc and add or update a dependency and you wouldn't have gotten the latest dependencies.

There is been a lot of discussion, I may have missed something. But I thought "doing development on some old version of rustc" was not one of our recommended workflows. I thought we were strongly encouraging everyone to be using latest stable/nightly for doing day-to-day development. Did I miss something?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For myself, I would put it at "encourage" but not "strongly encourage". There are close enough to valid reasons to be on old toolchains or else rust-toolchain.toml files wouldn't be a thing (outside of nightlies).

On a basic level, there is the casual contributor who happens to have not upgraded to the latest yet. On the other end is people using a specific toolchain, for reasons, and needing to patch their dependency.

There is a lot of ins and outs to all of this where we can weigh different parts of this to say "under these circumstances but what about those circumstances. I tried instead to keep things simple.


### Hard-error

Instead of *preferring* MSRV-compatible dependencies, the resolver could hard error if only MSRV-incompatible versions are available.
- `--ignore-rust-version` would need to be "sticky" in the `Cargo.lock` to avoid the next run command from rolling back the `Cargo.lock` which might be confusing because it is "out of sight; out of mind".
- To avoid `Cargo.lock` churn, we can't fallback to `rustc --version` when `package.rust-version` is not present

In addition to errors, differences from the "preference" solutions include:
- Increase the chance of an MSRV-compatible `Cargo.lock` because the resolver can backtrack on MSRV-incompatible transitive dependencies, trying alternative versions of direct dependencies
- When workspace members have different MSRVs, dependencies exclusive to a higher MSRV package can use higher versions

To get the error reporting to be of sufficient quality will require major work in a complex, high risk area of Cargo (the resolver).
This would block stabilization indefinitely.
We could adopt this approach in the future, if desired

Effects on workflows (including non-resolver behavior):
1. Latest Rust with no MSRV
-`cargo new` setting `package.rust-version = "tbd-name-representing-currently-running-rust-toolchain"` moves most users to "Latest Rust as the MSRV" with no extra maintenance cost
- ❌ Dealing with incompatible dependencies will have a friendlier face because the hard build error after changing dependencies is changed to a notification during update suggesting they upgrade to get the new dependency because we fallback to `rustc --version` when `package.rust-version` is unset (as a side effect of us capturing `rust-toolchain.toml`)
2. Latest Rust as the MSRV
- ✅ Packages can more easily keep their MSRV up-to-date with
- `package.rust-version = "tbd-name-representing-currently-running-rust-toolchain"` (no policy around when it is changed) though this is dependent on your Rust toolchain being up-to-date (see "Latest Rust with no MSRV" for more)
- `cargo update --update-rust-version` (e.g. when updating minor version) though this is dependent on what you dependencies are using for an MSRV
- ✅ Packages can more easily offer unofficial support for an MSRV due to shifting the building with MSRV-incompatible dependencies from an error to a `deny` diagnostic
3. Extended MSRV
-`Cargo.lock` will Just Work for `package.rust-version`
- ❌ Application developers using `rust-toolchain.toml` will have to duplicate that in `package.rust-version` and keep it in sync
4. Extended published MSRV w/ latest development MSRV
- ❌ A design not been worked out to allow this workflow
- ❌ If this is done unconditionally, then the `Cargo.lock` will change on upgrade
- ❌ This is incompatible with per-`feature` MSRVs

# Prior art
[prior-art]: #prior-art

- Python: instead of tying packages to a particular tooling version, the community instead focuses on their equivalent of the [`rustversion` crate](https://crates.io/crates/rustversion) combined with tool-version-conditional dependencies that allow polyfills.
- We have [cfg_accessible](https://github.com/rust-lang/rust/issues/64797) as a first step though it has been stalled
- These don't have to be mutually exclusive solutions as conditional compilation offers flexibility at the cost of maintenance. Different maintainers might make different decisions in how much they leverage each
- One big difference is Python continues to support previous releases which sets a standard within the community for "MSRV" policies.
- [PHP Platform Packages](https://getcomposer.org/doc/01-basic-usage.md#platform-packages) is a more general mechanism than MSRV that allows declaring dependencies on external runtime requirements, like the interpreter version, interpreter extensions presence and version, or even whether the interpreter is 64-bit.
- Resolves to current system
- Can be overridden to so current system is always considered compatible
- Not tracked in their lockfile
- When run on an incompatible system, it will error and require running a command to re-resolve the dependencies for the current system
- One difference is that PHP is interpreted and that their lockfile must encompass not just development dependencies but deployment dependencies. This is in contrast to Rust which has development and deployment-build dependencies tracked with a lockfile while deployment uses OS-specific dependencies, like shared-object dependencies of ELF binaries which are not locked by their nature but instead developers rely on other technologies like docker or Nix (not even static linking can help as they that still leaves them subject to the kernel version in non-bare metal deployments).

# Unresolved questions
[unresolved-questions]: #unresolved-questions

The config field is fairly rough
- The name isn't very clear
- The values are awkward
- Should we instead just have a `resolver.rust-version = true`?
- If we later add "resolve to toolchain" version, this might be confusing.
- Maybe enumeration, like `resolver.rust-version = <manifest|toolchain|ignore>`?

`rust-version = "tbd-name-representing-currently-running-rust-toolchain"`'s field name is unsettled and deciding on it is not blocking for stabilization.
Ideally, we make it clear that this is not inferred from syntax,
that this is the currently running toolchain,
that we ignore pre-release toolchains,
and the name works well for resolver config if we decide to add "resolve to toolchain version" and want these to be consistent.
Some options include:
- `"tbd-name-representing-currently-running-rust-toolchain"` can imply "infer from syntactic minimum"
- `latest` can imply "latest globally (ie from rust-lang.org)
- `stable` can imply "latest globally (ie from rust-lang.org)
- `toolchain` might look weird?
- `local` implies a `remote`
- `current` is like `latest` but a little softer and might work

Resolving with an unset `package.rust-version` falls back to `rustc --version` only if its a non-pre-release.
Should we instead pick the previous stable release (e.g. nightly 1.77 would resolve for 1.76)?

Whether we report stale dependencies only on `cargo update` or on every command.
See "Syncing `Cargo.toml` to `Cargo.lock` on any Cargo command".

# Future possibilities
[future-possibilities]: #future-possibilities

## Integrate `cargo audit`

If we [integrate `cargo audit`](https://github.com/rust-lang/cargo/issues/7678),
we can better help users on older dependencies identify security vulnerabilities,
reducing the risks associated with being on older versions.

## "cargo upgrade"

As we pull [`cargo upgrade` into cargo](https://github.com/rust-lang/cargo/issues/12425),
we'll want to make it respect MSRV as well

## cargo install

`cargo install` could auto-select a top-level package that is compatible with the version of rustc that will be used to build it.

This could be controlled through a config field and
a smaller step towards this is we could stabilize the field
without changing the default away from `maximum`,
allowing people to intentionally opt-in to auto-selecting a compatible top-level paclage.

Dependency resolution could be controlled through a config field `install.resolver.precedence`,
mirroring `resolver.precedence`.
The value add of this compared to `--locked` is unclear.

See [rust-lang/cargo#10903](https://github.com/rust-lang/cargo/issues/10903) for more discussion.

**Note:** [rust-lang/cago#12798](https://github.com/rust-lang/cargo/pull/12798)
(released in 1.75) made it so `cargo install` will error upfront,
suggesting a version of the package to use and to pass `--locked` assuming the
bundled `Cargo.lock` has MSRV compatible dependencies.

## cargo publish

If you publish a library using your MSRV and MSRV-incompatible dependencies exist, the publish verification step will fail.
You can workaround this by
- Upgrading
- Running with `--no-verify`

See [rust-lang/cargo#13306](https://github.com/rust-lang/cargo/issues/13306).

## `resolver.precedence = "rust-version=<X>[.<Y>[.<Z>]]"`

We could allow people setting an effective rust-version within the config.
This would be useful for people who have a reason to not set `package.rust-version`
as well as to reproduce behavior with different Rust versions.

## rustup supporting `+msrv`

See https://github.com/rust-lang/rustup/issues/1484#issuecomment-1494058857

## Language-version lints

We could make developing with the latest toolchain with old MSRVs easier if we provided lints.
Due to accuracy of information, this might start as a clippy lint, see
[#6324](https://github.com/rust-lang/rust-clippy/issues/6324).
This doesn't have to be perfect (covering all facets of the language) to be useful in helping developers identify their change is MSRV incompatible as early as possible.

If we allowed this to bypass caplints,
then you could more easily track when a dependency with an unspecified MSRV is incompatible.

## Language-version awareness for rust-analyzer

rust-analyzer could mark auto-complete options as being incompatible with the MSRV and
automatically bump the MSRV if selected, much like auto-adding a `use` statement.

## Establish a policy on MSRV

For us to say "your MSRV should be X" would likely be both premature and would have a lot of caveats for different use cases.

With [rust-lang/cargo#13056](https://github.com/rust-lang/cargo/pull/13056),
we at least made it explicit that people should verify their MSRV.

Ideally, we'd at least facilitate people in setting their MSRV. Some data that could help includes:
- A report of rust-versions used making requests to crates.io as determined by the user-agent
- A report of `package.rust-version` for the latest versions of packages on crates.io
- A report of `package.rust-version` for the recently downloaded versions of packages on crates.io

Once people have more data to help them in picking an MSRV policy,
it would help to also document trade-offs on whether an MSRV policy should proactive or reactive on when to bump it.

## Warn when adding dependencies with unspecified MSRVs

When adding packages without an MSRV,
its not clear whether it will work with your project.
Knowing that they haven't declared support for your toolchain version could be important,
after we've made it easier to declare an MSRV.

## Track version maintenance status on crates.io

If you `cargo add` a dependency and it says that a newer version is available but it supports a dramatically different MSRV than you,
it would be easy to assume there is a mismatch in expectations and you shouldn't use that dependency.
However, you may still be supported via an LTS but that information can only be captured in documentation which is not within the flow of the developer.

If crates.io had a mutable package and package version metadata database,
maintainers could report the maintenance status of specific versions (or maybe encode their maintenance policy),
allowing cargo to report not just whether you are on latest, but whether you are supported.