0
votes

Since this issue involves multiple dependencies and I'm not sure in which direction to look for reproducing it, I hope it is fine to ask it based on a specific example. The more general pattern behind this question is: There is a dependency chain user => libExtension => libBase and libExtension and libBase are incompatible.

In the geo-booleanop project, we run into an interesting issue that we cannot fully understand. It involves the interplay of the following packages:

  • geo-types (libBase): A core library that provides basic types like lines, rects, polygons, etc.
  • geo-booleanop (libExtension): A library building on top of geo-types, extending it with one new functionality: A new trait BooleanOp that has functions like intersection/union/... and is implemented for the polygon type from geo-types.
  • Any user package that pulls together geo-types and geo-booleanop with the intention of using the BooleanOp trait for polygons.

The package geo-types recently had a breaking change going from version 0.4 to 0.5, e.g., it changed some of the fields of its Rect type from pub fields to getters.

The geo-booleanop package is not yet adopted to this change, i.e., it still makes explicit use of these pub fields. Currently, the Cargo.toml of geo-booleanop specifies geo-types = "0.4", and an attempt to bump it to 0.5 and doing cargo build within geo-booleanop leads to compiler errors at the pub field accesses as expected.

Now the surprising thing: When creating a user package that combines the latest versions of geo-types and geo-booleanop, we see the following output from cargo build in the user package:

$ cargo build
[...]
   Compiling geo-booleanop v0.2.1
   Compiling geo-types v0.5.0
[...]

error[E0599]: no method named `union` found for type `geo_types::polygon::Polygon<{float}>` in the current scope
  --> src/main.rs:21:23
   |
21 |     let union = poly1.union(&poly2);
   |                       ^^^^^ method not found in `geo_types::polygon::Polygon<{float}>`

That is surprising because:

  • Compiling geo-booleanop v0.2.1 in combination with geo-types v0.5.0 should not be possible at all, because they are incompatible.
  • The error about no method named union is confusing, because the user code does everything right to bring the BooleanOp trait into scope, which should provide exactly that method for polygons.
  • Ironically the compiler also outputs a warning that the BooleanOp trait import is unused.

My questions are:

  • Why is cargo build not showing compilation errors highlighting the incompatibility of the dependencies?
  • If that is by design, is there anything we could do on geo-booleanop site to make such problems easier for our users to grasp? Currently they do everything right in terms of the written code to bring the trait into scope, and yet the compiler behaves as if the trait is missing -- whereas in reality it is a version mismatch. Can we enforce a more explicit error on library site?

Details in case they matter:

  • The BooleanOp trait is generic in its underlying floating point type (see implementation). Is this causing the problem, because generics do not get instantiated until client code uses it like in other programming languages?
  • This is the reproducing client code: main.rs and Cargo.toml.
1

1 Answers

1
votes

I summarize your scenario as follows:

  • your Cargo.toml depends on geo-booleanop 0.2.1 and geo-types 0.5.0
  • geo-booleanop 0.2.1 depends on geo-types 0.4.x

If you look at your Cargo.lock file, you would notice that there are two [[package]] entries for geo-types, one for 0.4.x and one for 0.5.0.

When two (transitive) dependency requirements of the same project are incompatible, cargo actually attempts to compile both of them. Cargo does not expose indirect dependencies to your crate, so Cargo is building both versions separately as if they don't have the same crate name. As long as you don't try to use geo-types with geo-booleanop, they can actually work independently.

For clarity I call the two versions geo_types_04 and geo_types_05 respectively. geo-booleanop works on a dependency called geo_types_04, where geo_types_04 is not exposed to your crate, so you don't need to care if geo_types_04 is used at all.

It only becomes a problem when geo-booleanop exposes a function that accepts/returns some type defined in geo_types_04. Then you assume it is geo_types_05, resulting in incompatibility.

Your error this time is actually one of the more readable ones.Sometimes you would get errors like Expected geo_types::Line, got geo_types::Line because the function accepting geo_types_04::Line is called with a geo_types_05::Line.