Updating Cargo.toml with minimal versions

I'm reading Rust for Rustaceans (Gjengset, 2021) and the book recommends setting minimal versions on all the crate dependencies when making library crates. The reason is to not force users to newer versions, to avoid e. g. incompatibility with other crates.

The right strategy is to list the earliest version that has all the things your crate depends on and to make sure that this remains the case even as you add new code to your crate. But how do you establish that beyond trawling the changelogs, or through trial and error? Your best bet is to use Cargo’s unstable -Zminimal-versions flag, which makes your crate use the minimum acceptable version for all dependencies, rather than the maximum. Then, set all your dependencies to just the latest major version number, try to compile, and add a minor version to any dependencies that don’t.

So I have installed cargo-minimal-versions and I'm running cargo minimal-versions check. I have added two dependencies to my dev dependencies to bump minimal versions in transitive dependencies, to fix errors, and the check now passes.

However, I realized that my Cargo.toml still have all the new versions and not the ones that the command downgrades to.

Right now I'm comparing the "Downgrading " output to my Cargo.toml versions, but is there a more automated way?

Dev dependencies are ignored entirely when a library is published, so this won't work for ensuring they're new enough.

If they are your direct dependencies, you should bump them in the regular dependencies section.

If you're working around breakage in transitive dependencies, then it shouldn't be your job to fix the requirements - you should file a bug with whatever library didn't specify its minimum dependency versions high enough. You can still add transitive dependencies redundantly to your own library to bump their minor version, but you won't be able to affect major version (in Cargo 0.x is major too).

For fresh setups without Cargo.lock, Cargo defaults to picking newest allowed versions of dependencies, so too-low version requirements won't appear to be a problem (this is also why minimal-versions is so bitrotten in the ecosystem! People specify serde = "1", but their code won't work with a 9-year-old 1.0.0)

It breaks when someone already has old common deps in their Cargo.lock and locked versions don't get bumped as they should when another dependency is added.

A newer, less rigorous approach than -Zminimal-versions is to use cargo +nightly update -Zdirect-minimal-versions. “Direct” minimal versions means that Cargo will select minimal versions for crates.io packages which your workspace depends on, but ordinary (maximal) versions for packages which those packages depend on. This way, you can get your packages’ dependency versions closer to correct even when your dependencies aren’t themselves minimal-versions-clean.

I had read the text I myself quoted properly :person_facepalming: . I have to set lower versions in Cargo.toml and then see if cargo minimal-versions check (and build?) fails. :light_bulb:

I now proceeded to set versions to just keep the version string up to the first non-zero number. I discovered that I could set versions lower than I supported, due to other dependencies "holding them up", so I had to iterate a bit. (serde = "1" was working!, until I lowered other deps :stuck_out_tongue: )

I managed to squeeze down my versions a fair bit :slight_smile: , and have filed on issue so far.

diff --git a/Cargo.toml b/Cargo.toml
index 67bf134..330fcfb 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -24,7 +24,7 @@ __dev = []
-async-trait = "0.1.89"
-axum = "0.8.8"
-derive_more = { version = "2.1.1", features = ["as_ref", "display"] }
-etcetera = "0.11.0"
-keyring-core = { version = "1.0.0", optional = true }
-reqwest = "0.13.2"
-rmcp = { version = "1.7.0", default-features = false, features = [
+async-trait = "0.1"
+axum = "0.8"
+derive_more = { version = "2", features = ["as_ref", "display"] }
+etcetera = "0.11"
+keyring-core = { version = "1", optional = true }
+reqwest = "0.13"
+rmcp = { version = "1.7", default-features = false, features = [
@@ -41 +41 @@ rust_decimal = { version = "1.40.0", features = ["macros"] }
-serde = "1.0.228"
+serde = "1.0.221"
@@ -43,5 +43,5 @@ serde = "1.0.228"
-serde_json = { version = "1.0.149", features = ["preserve_order"] }
-serde_urlencoded = "0.7.1"
-serde_with = "3.18.0"
-thiserror = "2.0.18"
-tokio = { version = "1.50.0", features = [
+serde_json = { version = "1.0.100", features = ["preserve_order"] }
+serde_urlencoded = "0.7"
+serde_with = "3"
+thiserror = "2"
+tokio = { version = "1.40", features = [
@@ -53,3 +53,12 @@ tokio = { version = "1.50.0", features = [
-tracing = "0.1.44"
-uuid = { version = "1.22.0", features = ["serde"] }
-webbrowser = "1.2.0"
+tracing = "0.1"
+uuid = { version = "1", features = ["serde"] }
+webbrowser = "1"
+
+# Avoid circular dependency rust_decimal_macros <-> rust_decimal
+## https://github.com/paupino/rust-decimal/issues/810
+rust_decimal_macros = "1.37"
+
+num-bigint = "0.4.2"
+
+# Force minimal to enable the tokio sync feature for rmcp
+tokio-stream = { version = "0.1", features = ["sync"] }

edit: Dropped <details> for the patch, as code formatting did not work.

Tip: If you use -Zdirect-minimal-versions, this becomes an error (if your version is not at least as high as is required in the dependency graph, that is reported as a conflict).

The trick to mixing HTML block elements and Markdown is that you need to put blank lines between the HTML tags and the Markdown content. Like this:

<details>

```rust
Example
```

</details>

I would recommend running security check on proposed new minimal versions.
Not sure it is good advice to downgrade versions on first time you are about to publish a crate. The main item you should be checking is -Zminimal-versions does not break your crate on its own; as an existing project may which to add it and will have a Cargo.lock which by default will only make minor changes to.