Liam
Liam

Reputation: 401

Making lower level crates available at the top using Cargo

Preface: I'm fairly certain that this should be something easy to figure out, but I haven't had any luck. I've actually been struggling in general with the Cargo manager, I keep wanting it to be like a simple include statement in C, but it is, of course, nothing so simple. If you have comments on how to structure this project better in general, please let share them.

Lets say I have a library in Rust which is managed with Cargo. The crate is called point and the directory looks like this.

point/
├── Cargo.lock
├── Cargo.toml
├── src
    └── lib.rs

This crate has no dependencies.

Now, I have built another library which will use this point crate. This library is called sat This library looks like this:

sat/
├── Cargo.lock
├── Cargo.toml
├── src
    ├── circle.rs
    ├── lib.rs
    ├── point/
    └── polygon.rs

Note that point/ is the point directory mentioned above. The reason I included point as a separate library, instead of as a module within sat, is that both the circle and polygon modules depend on point (I couldn't figure out a way to get point to work as a module in sat without repeating code. This is not truly relevant to the question, but is relevant to idiomatic Rust library structures, so feel free to comment on better ways to set this up).

Here is the Cargo.toml file for sat/

$ cat sat/Cargo.toml 
[package]
name = "sat"
version = "0.1.0"

[dependencies]
point = { path = "src/point" }

Now, all of this is well and good. But, let us say that I want to create an application that uses sat as an external crate. How can I access the point library in this application without having to include the point library itself?

Here is an example, the point library has a struct called Point in it. The sat library has a struct called Circle in it. Let's say my example source code looks like this:

$ cat src/main.rs

extern crate sat;

// I don't want to have to include the point crate because it is already 
// included in sat
// extern crate point;

fn main() {

    // declare a polygon
    // structure is: crate::module::struct::staticFunction
    let polygon = sat::polygon::Polygon::new( <parameters> );

    // I cannot declare a Point, because point is not a module in sat
    // this errors out.
    // However, this is the sort of thing that I would like to do.
    let point = sat::point::Point::new( <parameters> );

}

Upvotes: 0

Views: 382

Answers (1)

Shepmaster
Shepmaster

Reputation: 431669

I don't want to have to include the point crate because it is already included in sat

(emphasis mine)

That doesn't really mean anything. It's entirely possible (and a desired feature) that the version of a crate that you use can be a different version of a crate that a dependency is using. This allows you to use features from a newer version while a dependency hasn't updated yet (or vice versa). This prevents one specific type of "dependency hell".

Unfortunately, it introduces another kind of dependency hell where a public interface of crate A exposes a type from crate B (version 1) and we are trying to use crate A's public interface with crate B (version 2). This produces a range of confusing errors like "expected Foo, found Foo". These messages are being actively worked on.

The key thing to realize is that by putting an external type in your public API, your public API is now affected by the external type. This means that when the external crate bumps versions, you need to bump your version to maintain semantic versioning!

This latter case is what you are attempting to opt into.

You have two options:

  1. Re-export the types you need.
  2. Document that people need to use the same crate and the same version.

The first looks something like this:

point/src/lib.rs

pub struct Point(pub u8, pub u8);

sat/src/lib.rs

extern crate point;

pub use point::Point;

pub struct Circle(pub point::Point);

app/src/main.rs

extern crate sat;

use sat::{Point, Circle};

fn main() {
    let p = Point(0, 0);
    let c = Circle(p);
}

This is probably the closest to what you were looking for. Otherwise, you need to explicitly add the dependent crate to both sat and app. This isn't unheard of, most of the crates that play with hyper do the same thing.


The reason I included point as a separate library, instead of as a module within sat, is that both the circle and polygon modules depend on point. I couldn't figure out a way to get point to work as a module in sat without repeating code.

You should probably figure that out. Crates are great and you should certainly use them when you have a piece of reusable code, but they aren't the only way of reusing code:

pub mod point {
    pub struct Point(pub u8, pub u8);
}

pub mod square {
    use point::Point;

    pub struct Square(pub Point, pub Point);
}

pub mod circle {
    use point::Point;

    pub struct Circle(pub Point);
}

fn main() {
    let c = circle::Circle(point::Point(0, 0));
    let s = square::Square(point::Point(0, 0), point::Point(1, 1));
}

Upvotes: 2

Related Questions