I have a project which has dependencies on several crates which are workspaces of another repository. I made a fork of that repository so I can refer to one of those workspace crates with changes (so far none made).
My project Cargo.toml is fine just referring to those crates using crates.io. I then change one of the dependencies to refer to the local version of sn_cli, as follows:
And cargo now generates an error (below) which I don't understand, for two reasons:
firstly it seems to say that sn_node depends on walkdir ~2.4.0 when it actually has ~2.5.0 in its Cargo.toml.
secondly, even if sn_node did specify walkdir ~2.4.0, I think the version already picked up (2.5.0) for sn_cli would match?
Can anyone clarify what is wrong about my statements above, or shed any light on why this error should appear only when I refer to the local crate repo?
Updating crates.io index
error: failed to select a version for `walkdir`.
... required by package `sn_node v0.105.3`
... which satisfies dependency `sn_node = "^0.105.3"` of package `awe v0.1.0 (/home/mrh/src/safe-browser/awe/src-tauri)`
versions that meet the requirements `~2.4.0` are: 2.4.0
all possible versions conflict with previously selected packages.
previously selected package `walkdir v2.5.0`
... which satisfies dependency `walkdir = "~2.5.0"` of package `sn_cli v0.90.2 (/home/mrh/src/safe-browser/safe_network/sn_cli)`
... which satisfies path dependency `sn_cli` of package `awe v0.1.0 (/home/mrh/src/safe-browser/awe/src-tauri)`
failed to select a version for `walkdir` which could resolve this conflict
It's as if sn_node specifies ~2.4.0, but its Cargo.toml conains walkdir = "~2.5.0" (although specifyin ~2.4.0 should match 2.4 or 2.5 anyway)
trying to refer to a local fork (unchanged) of a repository which contains a number of workspaces.
The sn_node that is on crates.io does in fact depend on walkdir ~2.4.0: crates.io: Rust Package Registry. The copy in the main branch of their git repo had its dependency bumped to 2.5.0. If you are going to use a path dependency for sn_cli I suggest you do the same for sn_node to avoid this incompatibility.
Per the docs a tilde requirement like ~2.4.0 only allows 2.4.x, not 2.5.0.
If you're replacing a crates-io dependency with a local/git dependency, it's generally always a good idea to do so for all packages in the workspace being referenced, and to use a [dependency override] to also replace any transitive dependencies on the package(s) (so you don't end up with both the crates-io version and the patched version in your dependency tree for any reason).
90% of the time people reach for non-caret bound requirements, it's not the proper solution for what they want, and results in over-constraining version resolution. The usual reason given for using ~ constraints is msrv concerns (usually considered a minor change), but doing this will result in resolution breakage unless everyone always rapidly propagates a minor version update, bumping downstream requirements as appropriate.
Recommendation: When in doubt, use the default version requirement operator.
Avoid constraining the upper bound of a version to be anything less than the next semver incompatible version (e.g. avoid >=2.0, <2.4) as other packages in the dependency tree may require a newer version, leading to an unresolvable error.
My stance is a bit stronger than Cargo here: if you're publishing a library crate, please only use default (caret) dependencies unless the dependency is on a private implementation crate not meant to ever be externally depended on (i.e. a proc macro helper crate which is only a separate package for technical reasons). If you feel the need to provide tighter bounds to enforce transitive msrv, use a separate package with those ~ bounds (or bounds on your dependencies' msrv packages) so that these bounds are opt-in for bin packages that want them and don't restrict those that don't. (Or do the "major foundational library with LTS guarantees" thing and don't use any upstream code you don't control.)
(Also, you should always be using three-component bound specifications. Using a shorter bound (especially for non-caret bounds) is just vague without benefit, and makes the behavior more complicated to remember.)
Cheat sheet:
^ (default)
~
same major version
same minor version
allow minor changes
allow patch fixes
1.2.3
≥1.2.3, <2.0.0
≥1.2.3, <1.3.0
0.1.2
≥0.1.2, <0.2.0
≥0.1.2, <0.2.0
0.0.1
≥0.0.1, <0.0.2
≥0.0.1, <0.1.0†
0.0.0
≥0.0.0, <0.0.1
≥0.0.1, <0.1.0†
1.2
≥1.2.0, <2.0.0
≥1.2.0, <1.3.0
0.1
≥0.1.0, <0.2.0
≥0.1.0, <0.2.0
0.0‡
≥0.0.0, <0.1.0
≥0.0.0, <0.1.0
1
≥1.0.0, <2.0.0
≥1.0.0, <2.0.0†
0‡
≥0.0.0, <1.0.0†
≥0.0.0, <1.0.0†
† - weaker than often expected
‡ - just don't ever do this, please
My one minor complaint with these rules is that since ^ recognizes zerover but ~ doesn't, Cargo effectively treats 0.x.y as 0.MAJOR.PATCH (losing the ability to make minor releases) despite people using it as 0.MAJOR.MINOR (losing the ability to distinguish minor releases from patch).