4

How can I use a trait from a transitive dependency crate in my application?

Here's a minimal illustrative example of the problem I've run into:

In Cargo.toml, I have:

[dependencies]
mersenne_twister = "1.1.1"
rand = "0.8.5"

My crate depends on rand ^0.8.5 and mersenne_twister ^1.1.1, which itself depends on rand >=0.3, <0.5:

my-crate ---> rand 0.8.5
         |
         |
         ---> mersenne_twister 1.1.1  ----> rand >= 0.3, < 0.5

In my app, I'd like to use the implementation of trait rand::Rng for mersenne_twister::MT19937. But when I try bringing this trait into scope, it's apparently not recognized:

use mersenne_twister::MT19937;
use rand::Rng;

fn main() {
    let mut rng = MT19937::new_unseeded();
    let mut buffer = vec![0; 0xFFFF];

    // error[E0599]: no method named `fill_bytes` found for struct `MT19937` in the current scope
    rng.fill_bytes(&mut buffer);
}

My guess is that the Rng trait imported by use rand::Rng; is the one from rand 0.8.5, not the one from rand 0.4.6 that is actually implemented for MT19937, and that even though they are spelled the same way, they are distinct and unrelated traits and therefore cannot be referenced interchangeably.


So I have some questions:

  1. How can I use the Rng trait that works for MT19937 in my app? I don't think I can downgrade my dependency on rand to 0.4.6 in Cargo.toml because I need to use rand 0.8.5 elsewere in my app.
  2. In general how should traits defined in transitive dependencies be used?
  3. Is mersenne_twister's API design bad practice for not re-exporting Rng?

2 Answers 2

8

You can add multiple incompatible versions of a dependency to Cargo.toml by renaming one of them.

[dependencies]
mersenne_twister = "1.1.1"
rand = "0.8.5"
old_rand = { package = "rand", version = "0.4.6" }

But for this situation, there's already a better solution: use the MT19937 crate, which depends on the latest version of rand_core. RNG implementations should depend on rand_core instead of rand since all they need is the Rng trait and not any of the extras in rand, like distributions.

Sign up to request clarification or add additional context in comments.

5 Comments

Adding multiple version of the same crate as direct dependencies via renaming was my first thought, but it seemed very smelly to me so I wanted to avoid it. Is doing this the canonical method of dealing with this dependency problem?
Also great call on crate mt19937, this solves my problem exactly. Although, I find it hard to discover: I searched for Mersenne Twister on crates.io and it didn't come up and if you hadn't told me about it, I probably never would have discovered it. I hope someday crates.io can do a better job with crate discovery.
@RBF06 I would say the most canonical would be submitting a PR to the dependency, which ensures everyone benefits, but the above solution is guaranteed to work immediately. You could also copy the code into your project directly (license permitting) and fix it there, or if you aren't planning on publishing your crate to crates.io, you can use [patch].
are you saying that in general, if public crate A implements a trait from a public crate B on a public type, then crate A should publish a new non-backwards compatible version every time B does?
Pretty much, although in this case it would be changing it to depend on rand_core as well, which looking back, actually came after the last version of mersenne_twister. I suspect the reason that mersenne_twister hasn't been updated is that mt19937 exists. Cargo makes it easy to depend on old versions, so publishing a backwards-incompatible version should be completely beneficial when the alternative is publishing nothing.
5

I don't think I can downgrade my dependency on rand to 0.4.6 in Cargo.toml because I need to use rand 0.8.5 elsewere in my app.

You can use both, by renaming one or both versions.

[dependencies]
rand4 = { package = "rand", version = "0.4.6" }
rand8 = { package = "rand", version = "0.8.5" }

Then you would refer to rand4::Rng or rand8::Rng in your code as needed.

In general how should traits defined in transitive dependencies be used?

Is mersenne_twister's API design bad practice for not re-exporting Rng?

Re-exporting is convenient and often recommended, but regardless of whether they do so, it would be a semver breaking change for mersenne_twister to stop implementing rand::Rng for any version of rand it was already implemented for, so you don't have to worry about your code breaking.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.