Why we need to specify all dependencies (including transitives) in Rust?

Viewed 1289

For example, my project depends on crate A, A depends on B, then my project can't use crate B until I put B in the dependencies section of my project's Cargo.toml.

In Java if you're using Maven or Gradle, you can directly use B and don't need to declare it in the pom.xml or build.gradle. Why didn't cargo follow that path?

1 Answers

The reason is semver compatibility.

For example, say your crate depends on A with version 1.0, which depends on B with version 1.0:

your_crate  -->  A 1.0  -->  B 1.0
  • Now, B publishes a new version 2.0 that is not compatible to version 1.0.
  • A upgrades to B version 2.0 and publishes a minor version 1.1. This version is compatible with 1.0, even though its dependency is not.
  • You upgrade to A version 1.1.
your_crate  -->  A 1.1  -->  B 2.0

The problem is, if you were able to access dependencies transitively, your code would break, because B 2.0 is not compatible with B 1.0.


There are two solutions to this:

  1. The maintainer of A can re-export everything in B:

    // crate a
    pub use b;
    

    Like in Java, this allows you to access items of the transitive dependency B:

    // your crate
    use a::b;
    

    However, this means that A can't release a minor version that includes a major version bump in B. So when B releases version 2.0, A also has to release a new major version.

  2. You can depend on B, too. This means that Cargo will try to choose a version of B that satisfies the requirements of both A and your crate. If it isn't able to, several versions of B are included:

    your_crate  -->  A 1.1  -->  B 2.0
                -->  B 1.0
    
Related