RFC API guideline - re-export a whole crate if exporting any

I recently ran into a problem using quickcheck, because it re-exports rand::Rng as part of its API. However to use it, it means that any user of quickcheck must take an explicit dependency on the same version of rand that quickcheck itself does. This precludes the quickcheck user from using a different version of rand itself. This is particularly acute because rand-0.5 has quite a few API changed from rand-0.4. which quickcheck currently uses

The fix for quickcheck is very straightforward, but it occurs to me that this is a general pattern. If a crate is re-exporting another crate's symbols as part of its API, it should also re-export the whole thing for use by downstream crates. This is particularly acute for crates like rand, as rand::Rng is just a trait, so any use of it must use the rest of the crate (or define your own Rng instances, I guess).

@BurntSushi asked in the comments on my PR:

Do you know of any precedent with other crates that do this? For example, should every crate that derives serde::Deserialize (or serde::Serialize) also re-export all of serde?

Which is a good question. I hadn't encountered this situation until quickcheck/rand, but it must have occurred elsewhere. Or maybe its just rare for crates to rexport symbols as part of their API?

For the serde case, my intuition is that it would be rare to depend on two crates using the same version of the Serialize/Deserialize traits, and they work just as well if they're independent. Or maybe its easier for everyone to use a common version of the crates because the API is more stable than (say) rand?

(edit: I also created an api-guidelines issue)

4 Likes

AFAIK this is primarily only an issue for crates that are versioned below 1.0, right? Below 1.0 semver rules say that minor version changes are incompatible, so rand 0.4 and 0.5 are not compatible. This would not be the case if rand had published a 1.0 and the versions in question were 1.4 and 1.5. This is the primary motivator behind the libz blitz pushing widely-used crates to 1.0--it's hard to have a usable ecosystem if you have dependencies like this.

1.0 is a red herring. What matters is semver incompatibility.

Even if we were talking about rand-1.4 vs rand-1.5 which were semver compatible, it would still be annoying not to be able to use new features in rand-1.5 just because quickcheck tied you to rand-1.4.

2 Likes

OK, this is fair. I suspect it's just more likely to happen in our current world of mostly pre-1.0 crates. (I was going to try to find an example with post-1.0 crates but there are so few of them that I gave up!)

1 Like

Yes, I came to the same conclusion. Export whole crate if you publicly depend on any of its features.

I'm doing the same with exporting failure, so that downstream users can access .causes() without also adding the failure crate.

It's not even that semver incompatibility might make it impossible to use, it just feels wrong that you may have to add two or more dependencies and extern crates to use one.

3 Likes