It is currently the case that every cargo package corresponds to exactly 0 or 1 library crates. Its not possible to use cargo to create a package containing multiple libraries. @alexcrichton and @wycats discussed the motivation for this back in 2015:
These comments enumerate some of the design constraints and drawbacks of allowing a single package to contain multiple libraries, but they also contain some motivations for the limitation that I think might be reasonable to challenge at this point.
That is, I think there are good reasons to want to have multiple libraries in a package that are uploaded to crates.io as a single unit:
Proc macro crates: Proc macros have to be contained in a special crate, leading to splits like
serde_derive. With macro re-exporting, though, its possible to expose proc macros through the non-macro crate. In theory, the “derive” crate could be completely eliminated by having it subsumed as a “sub-library” of the main crate.
Internal privacy boundaries: Often, a project will have a subsection which exposes a simple interface for very complex internals. Many items inside that submodule will want to be exposed throughout it but not outside it, leading to a lot of
pub(in module)declarations. These could be abbreviated to
pub(crate)(or even further if we stabilize
cratevisibility or similar) by making that submodule its own crate.
- Improved internal dependency organization: Crates are required to form a DAG. By breaking off subcrates, you guarantee a certain relationship of dependencies between them, helping you maintain a certain order within your project.
- Improved compile times: Crates are compiled separately and in parallel; it can be worth making a module its own crate for that reason alone.
I don’t have a complete design, but I think a solution in this space is worth pursuing.
Here’s a list of design constraints I’ve come up with:
- These libraries should all be versioned and packaged together. When uploaded to crates.io, they form a single entry. If you want them to be separate, you want a workspace, not this feature.
- Largely as an implication of the first item, the design is constrained to having a “main” library (unless its a binary project), which is probably treated specially. I’m going to refer to the non-main libraries as “sublibraries.”
- There should be an easy and automatic way to do this without enumerating them all in your Cargo.toml, so that creating a new sublibrary is as easy as creating a new submodule. Ideally, each library need not have its own
- It should be possible (with annotations) for these subliraries to depend on one another.
- It should be possible for sublibraries to have dependencies that your other libraries don’t, but they should also have automatic access to the dependencies of the main library.
Underlying mechanism (
Cargo.toml gains a new section
[[sublib]], which is just like
[[bin]] et al. Every sublibrary is available as a dependency to the main library be default. A sublibrary does not need to have a
[[sublib]] section has a new entry the other target entries don’t have:
manifest-path, which points to a manifest for that sublibrary. A manifest for a sublibrary contains only a subset of
The sublibrary manifest contains the dependencies table. Somehow, through the dependencies table, a sublibrary can depend both on external packages and on other sublibraries (is a
path dependency adequate for the latter case or do we need a new type of dependency?).
The sublibrary manifest does not generate its own lockfile: all external dependencies are versioned in the main
Cargo.lock for the package.
Sublibrary manifests are optional: without one, that sublibrary has access to all the dependencies in the main library’s
Cargo.toml and none of the other sublibraries.
Additionally, the sublibrary manifest contains a
[lib] table, which has all the options that the main
[lib] table would have. Having a
[lib] table in the sublibrary manifest as well as a
[[sublib]] section is an error: only the implicit form, described below, uses the
Automated implicit form
src/lib/ directory acts a lot like the
src/bin directory. Every subdirectory of
src/lib is an automatic sublib with the name of that directory, rooted at
src/lib/$name/lib.rs. That directory can also contain a toml file for the sublibrary manifest path, maybe named
Cargo.toml but maybe named something else like
Sublibrary.toml or something?
As a result, a user can create a new sublibrary by creating a new directory under
src/lib, no other work necessary. When they want more complexity, they can create a toml file in that directory. Only if they want a different file structure do they need to move into the
[[sublib]] section in your Cargo.toml turns off the implicit form (just like
I’m sure there are already some projects with a toplevel module named
lib. Not sure the best way to handle this. Maybe we can act fast to reserve that directory name in the edition for now?
Revisiting Alex & Yehuda’s design problems:
- Building separate libraries: since sublibraries are targets, they can be built the same way any other target can; when building the main target, sublibraries will be built since they are dependencies of it.
- Specifying dependencies among them: handled by the sublibraries’ manifest file.
- Build scripts: Open question! I haven’t tried to solve this yet.
- Yehuda’s “semantic gap:” I think this comment refers to a version in which multiple libraries are exposed from a single crates.io package. My proposal avoids this problem by having a single “main” package that is exposed, and sublibraries are just for internal organization.