How can I use two versions of the same crate?

I want to specify two versions of the same crate to be used in a binary, but they are the same major version. E.g. foo-1.1.0 and foo-1.2.0.

These are semver compatible, so Cargo will pick one and only use that for both dependencies, even if I specify =1.1.0.

Is there any good way to tell Cargo to not do this? I need the exact versions I specified.

If they're "semver compatible", then why do you need both?

2 Likes

I want to build a tool for testing migrations between versions of libraries. Bugfixes can be breaking changes, but are semver compatible.

There is no way to do this in a single Cargo build. You will have to patch the crate source to change the version number of one (and I'd change the name too to avoid confusion). toml_edit can help you automate this.

It might also be possible to dynamically load two separately compiled cdylibs and invoke them individually, but that makes Rust testing harder.

Couldn't find a way to make this happen with crates.io, but git dependencies work.

[dependencies]
rand = { git = "https://github.com/rust-random/rand.git", tag = "0.8.5" }
rand_old = { git = "https://github.com/rust-random/rand.git", tag = "0.8.4", package = "rand" }
1 Like

By a strict interpretation of semver, this is not true— As soon as a crate is published with some behavior, you have to assume that downstream code may depend on that behavior, however erroneous it may be.

Changing that behavior (even to correct it) may break those downstream users' code. In practice, this is most likely to happen if they've written their own mitigation for the erroneous behavior which breaks when the original function starts working properly.

1 Like

That won't work for me since I want to make sure it works with custom registries, as well as crates where the git repo no longer exists.

This syntax works on crates.io dependencies, too:

[dependencies]
rand = "=0.8.5"
rand_old = { version = "=0.8.4", package = "rand" }

I don't think that's how it works. The = syntax tells Cargo to use exactly that version.

3 Likes

This interpretation of semver is more-or-less useless. Because everything can be observed in a language which allows you to turn pointer-to-function into pointer-to-data. You can just look on the generated code and use it to calculate very-important-seed for your program.

At this point we are entering realm of “that would be rather stupid use of API” and we are start discussion about what breaking change is semver-compatible and what is not semver-compatible.

Don't do that, then. Make temporary git-fork of the crate if everything else fails. Or stick with fixed version of crate till your would be ready to remove your workaround.

But don't expect that upstream would support bugs of old versions just to make your workarounds work. That's not how semver works. It literally couldn't work that way.

P.S. BTW these “let's look on the code of function and pull something from it” are not even hyphotetical issues. I worked on a project which needed to do that to work with mutexes on MacOS in the context where stack may not be available. Specifically because runtime needed to ensure that attempt to get fresh stack for
a task wouldn't race with another such attempt in another task. Since the only way to lock or unlock mutex on MacOS is to call a function… we disassembled libc function and used data from it. But we never expected that Apple wouldn't ever change that function, we needed test which verified that new version of MacOS still used code which we could disassemble and understand.

2 Likes

Surely the specific bits of generated code don't quality as behavior exhibited by a library.

Cargo will not compile if you use them on both due to a conflict.

I gave very practical example when it may make very practical sense. You may deicide whether your want to support it or not, but if you declare that everything should be compatible then it's unclear why tricks like what I described should be excluded.

Yes, semver is, to some degree, social thingie, not purely technical. And that's normal because it tries to facilitate cooperative development which is very much a social activity.

This is what happens if you try that:

error: failed to select a version for `rand`.
    ... required by package `rusttest v0.0.1`
versions that meet the requirements `=0.8.4` are: 0.8.4

all possible versions conflict with previously selected packages.

  previously selected package `rand v0.8.5`
    ... which satisfies dependency `rand = "=0.8.5"` of package `rusttest v0.0.1`

failed to select a version for `rand` which could resolve this conflict

Which like, idk, maybe isn't the best. Everything else in cargo treats semver as a thing that is useful but has no concrete restrictions. Maybe it's a bug, even?

1 Like

Yeah, unless I'm missing something, IMO Cargo is too trusting of semver in this case, and thinks it knows better than the user even if they use =.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.