How about changing [lib] to [[lib]], to allow multiple library in a crate?

Currently cargo does not support multiple library in a crate. So… you need to create a new project like Subproject: regex_macros Subproject: phf Other repo: postgres_macros

and use with

[dependencies]
regex = "*"
regex_macros = "*"

But what if cargo supports multi-lib crate, you can do like this

# in regex crate
[[lib]]
name = "regex"

[[lib]]
name = "regex_macros"
path = "src/syntax_ext/lib.rs"

and use with

[dependencies]
regex = "*"
#![plugin(regex_macros)]

extern crate regex;

This was initially considered when designing cargo, but we chose to only allow one for a variety of reasons:

  • If there are two libraries, how do you select only one to build if you don’t want both?
  • Encouraging separate projects for each crate means dependencies are generally more re-usable than if they were bundled together. Crates which want to provide multiple libraries almost always end up getting large enough that they should be separated anyway as well.
  • With multiple libraries in one package there would need to be a method of specifying dependencies amongst them.
  • A number of features have been added after-the-fact which have made multiple libraries tricky. For example, if a package has a build script, how does it know which crate to link the native libraries into?

I think I may be forgetting some reasons as well, and @wycats may be able to add some in too.

10 Likes

You got the crux of it in your list. For me, mapping extern crate declarations directly (in most cases) to a crate in crates.io won the day.

If crates.io crates had multiple Rust crates, we would have to (1) educate people about the semantic gap, and (2) provide a way to do the mapping in the Cargo.toml, which every user of the crate would need to do.

The alternative, having package authors break up their libraries into multiple crates.io crates, which we made very ergonomic by supporting multiple crates in a single git repo, seems like a small price to pay to maintain the simplicity of extern crate mapping.

8 Likes

What if we added package groups, where adding a dependency on the group adds a dependency on all the packages in the group. I’m thinking of something similar to Arch Linux’s package system (i.e. you can install individual packages in a group or the entire group itself).

1 Like

If there are two libraries, how do you select only one to build if you don’t want both?

  • Build only extern crateed library

Encouraging separate projects for each crate means dependencies are generally more re-usable than if they were bundled together. Crates which want to provide multiple libraries almost always end up getting large enough that they should be separated anyway as well.

` [[bin]] name= “peg”

[lib] name = “peg_syntax_ext” `

With multiple libraries in one package there would need to be a method of specifying dependencies amongst them.

  • Proposal usage: #![plugin(plugin1)] extern crate lib1; extern crate lib2;
  • OR limit max one ‘runtime library’ per crate,

A number of features have been added after-the-fact which have made multiple libraries tricky. For example, if a package has a build script, how does it know which crate to link the native libraries into?

  • By using ‘build’ attribute in ‘lib’ & ‘bin’?
  • But as this can break project structure, maybe ‘/build_src/scriptname.rs’?

I would like for bins and libs to behave the same. I don’t see any particular reason the restrictions and rules around both are different, and I find it frustrating that I have to contort my build setup around them.

Right now my multi-lib, multi-bin project is set up very awkwardly, with all bins sharing deps and being in my top-level project, and all libs being sub-projects. This feels very unnatural.

5 Likes

I have to agree with brson here. There are legitimate reasons to have multiple libs in a crate and it’s generally something that is supported by other systems out there as well.

@brson and @mitsuhiko, I definitely agree that there are use cases for having multiple libraries, but what I and @wycats were saying is that there are also good reasons to split them up across projects and maintain them separately. This gets the benefit of simplifying Cargo.toml, dependency structure within a package, and a 1:1 mapping of extern crate to “crate on crates.io”. As @wycats pointed out we also made it quite nice to have multiple libraries in a repository.

Do you two feel that these benefits of requiring only one library are outweighed by the desire to have more than one library in a package?

@alexcrichton If maintaining a 1:1 mapping between libraries and packages is important, than I would like to at least be able to use that same strategy for mapping my bins to Cargo.toml. As it is I have to manage my libs by putting them in their own projects and my bins by putting them in one single project. If I could just put every artifact in its own project, and so treat them consistently, I would be happier. I would be doubly-happier if the metadata for those subprojects could be inferred instead of typed explicitly.

You can put every artifact in its own project; AFAIK, there’s nothing stopping one having a single package per binary?

Honestly I’m not sure. The main reason this comes up are macros I think, and there it’s not quite clear what is to be gained from that being in a separate crate. However I understand the motivation.

Macros being in a separate crate is important. These macros (syntax extensions) require pulling in rustc and libsyntax crates, which are huge. Since they’re only used for expansion, the crate can be included for parsing via the #[plugin] attribute. This prevents linking the output against rustc and syntax.

This sounds like a fantastic idea!

When you say macros, do you mean macro_rules! macros or plugins? Currently I don't believe macro_rules! requires a separate crate, and plugins require a separate crate for technical reasons (cross compilation).

1 Like

@huon When I tried creating a project that put binaries in the subprojects, Cargo would not build them. It only builds libs from subprojects. Further, Cargo will not allow you to have a project that produces no artifacts, so you have to pick one bin to be blessed.

I have a use case in favor of being able to have multiple dylib outputs in the same crate: I have a neat stack built up, in total it’s 7 project, of which 3 in Rust, 1 in Java, and 3 in Elisp. The Java and 1 of the Elisp projects each depend on 1 of the Rust crates. Thus the 3 Rust projects are built “the right way”. Now if it was at least a small team of people doing all this, all would be fine. But since it’s just me (at least for now), in practice it’s a major pain in the butt as the fixed-cost overhead of pushing through even a tiny change is basically having to spend a couple of hours tying it all together. And that’s assuming there are no mistakes. If there are, everything since the mistake needs to be redone.

The 3 Rust projects could instead (at least conceptually) live in the same repo, be built be the same cargo build command, and life would be so much easier and faster for people with a use case like this.

@jjpe There’s no reason a single repo can’t contain multiple crates in a workspace. e.g. https://github.com/sfackler/rust-openssl

1 Like

The effect I’m looking for is to basically have to type cargo build once (perhaps preceded by a cargo update) and have all artifacts (a binary and 2 dynamic libs) built in 1 go. No fuss, no muss.

It seems like combining multiple crates in a repo with a workspace might give that effect, but I’m not sure about that. If it is, it’s a workable solution for me. Is anyone in a position to answer this question before I sink the time into making the change?

cargo build --all does that, yes.

1 Like

And I think --all is now implicit when running from a workspace, right?

I just tried it, and it sure doesn’t look that way.

When I omit the --all flag, it only builds my core crate, while with the -all flag it also builds the other crates in the repo.