Mehdi Akiki
Rust Failure Atlas / Cargo and dependencies

RFA-106 · Case file with fixtures · Case 78 of 694 · Cargo workspace evidence

When a Cargo Package Requires a Newer Rust Compiler

The rust-version field is an explicit minimum-supported-Rust contract. Verify why the package needs it, then upgrade the toolchain or select a compatible release; never lower the field without testing the claimed MSRV.

Reviewed
Rust
Rust 1.98.1, Cargo 1.98.1
Targets
all targets
Profiles
check, dev, release, test

Direct answer

What this Rust failure means

Why it happens
The package's explicit minimum-supported-Rust contract is not satisfied by the compiler selected through rustup, CI, or repository configuration.
First discriminating check
Record the actual rustc and Cargo versions, then identify whether the named package is direct, transitive, or pinned in the lockfile.

Cargo 1.98.1 refuses the failing package before compiling its small main.rs:

rustc 1.98.1 is not supported
[email protected] requires rustc 1.99

This message is not about the edition. The package uses edition 2024, which Rust 1.98 understands. It is about the rust-version field, commonly called the minimum supported Rust version, or MSRV.

Edition and rust-version answer different questions

The edition selects a coherent set of language behavior and migration rules. It is not a compiler release requirement by itself. Many compiler releases support the same edition.

The rust-version reference defines the minimum Rust version the package says it supports. Cargo uses it for diagnostics and, with compatible resolver behavior, dependency selection.

I keep these values separate in my head:

edition       = language compatibility context
rust-version  = oldest compiler promised by this package
toolchain     = compiler actually running now

Changing edition to 2021 does not make code using a Rust 1.99 API compile on 1.98. Likewise, a 2024-edition package can have an MSRV older than the newest stable compiler if its code and dependencies allow it.

Do not lower the field as a spelling fix

The repaired manifest declares Rust 1.98 because the fixture is actually compiled and verified on 1.98.1. In a real package, editing rust-version = "1.99" to "1.98" without this proof only removes Cargo's early protection.

The source may use a language feature, standard-library API, Cargo feature, or dependency that needs the newer release. A build script and proc macro also run as part of the package and belong to the check.

My valid repairs are normally:

  • update the selected toolchain to meet the package contract;
  • choose an older compatible package release;
  • replace the new API with an older equivalent and test the lower MSRV;
  • raise the application's own MSRV when the product accepts that change.

Which one is right depends on who owns the compatibility promise.

Resolution can use MSRV information

The Cargo resolver documentation explains how Rust-version-aware resolution can prefer dependency versions compatible with the workspace's Rust version. The exact behavior depends on resolver configuration and which package fields are present.

This does not make missing metadata safe. If a dependency omits or understates its MSRV, Cargo cannot infer every API used in its source. A lockfile created with a new toolchain can also pin a release that an older CI compiler cannot build.

For a workspace I define the intended MSRV clearly and test it. Mixed member policies are possible, but they need deliberate CI jobs and release rules rather than one vague “old Rust” promise.

Toolchain selection may differ by directory

rustc --version from one shell is not always the compiler Cargo uses in another directory. A rust-toolchain.toml, rustup override, editor configuration, container image, or CI action can select a toolchain.

I capture both commands in the failing environment:

rustc --version --verbose
cargo --version --verbose

I also record the directory and inspect rustup's active toolchain. This avoids fixing a global default while the repository keeps selecting another pinned release.

Applications and libraries have different pressure

An application team controls deployment and can upgrade its compiler as one coordinated change. A public library affects downstream users who may be pinned by Linux distributions, embedded vendors, or organizational policy.

For a library, raising MSRV is a compatibility event even if semantic versioning policy does not classify every MSRV increase in the same way. I publish the policy, test the stated compiler, and avoid accidental increases from dependencies.

For an application, I still pin the toolchain so local, CI, and release builds agree. “Latest stable” is a moving input, not a reproducibility strategy.

My debugging sequence

When Cargo reports an unsupported compiler, I check:

  1. The rustc and Cargo versions actually selected in the repository.
  2. The first package named by the message and whether it is direct or transitive.
  3. Its rust-version, release notes, and dependency requirements.
  4. The lockfile and resolver configuration.
  5. Whether upgrading the toolchain or selecting a compatible dependency fits the product policy.
  6. A clean build and tests on the claimed minimum compiler.

The field is not an obstacle to bypass. It is a machine-readable promise. The trustworthy fix makes the compiler, dependency graph, and published support policy tell the same story.