Reputation: 401
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
Reputation: 431669
I don't want to have to include the
point
crate because it is already included insat
(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:
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
andpolygon
modules depend onpoint
. I couldn't figure out a way to getpoint
to work as a module insat
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