Large project organization

Greetings! I have a substantial question that keeps me awake during the night :new_moon_with_face:. Let me give some context:

Rust provides several features to hierarchically (and not only hierarchically) organize a project: crates, modules and features. But great powers came with good responsibility, hence my question:

Is there any preference, idiom or guideline about preferring multiple crates for a particular project, or to prefer multiple modules inside the particular project?

With a particular project I'm thinking in the same git repository, for instance.

The options I see are as follows:

  • Single Crate: Module is preferred, crate if really needed.
    Composed mainly of modules and features. New crates are allowed, but only to encapsulate functionality that is either consuming the main crate, or extends the main crate (always direct dependency with the main crate).
  • Multiple Crates: Crate is preferred, module if really needed.
    Composed of several crates. Features may be several crates wide. API, implementations, even features are encapsulated in different crates. The different crates depend on some each other, but there is one that exposes the shared API.

I'm eager to see other ideas or proposals. Thanks for your answers in advance :slight_smile:

I would suggest avoiding large numbers of crates. Generally even a "large" project would equate to a single crate. As an example, tokio is a single crate ( I thinK! ).

( Well it isn't quite... is has one "tokio" dependency :

crates.io: Rust Package Registry )

1 Like

Assuming you have well defined and relatively stable boundaries between parts of your code, I personally prefer the multi-crate approach.

It has the following advantages:

  • It encourages development of stable API boundaries and proper tracking of breaking changes.
  • It becomes easier to separate work and responsibility in a team.
  • It has a bit faster development loop, i.e. rust-analyzer/clippy/rustfmt/cargo test need to check only one crate instead of a whole monolith.
  • Compilation can be faster too, since crates can be compiled in parallel.
  • Crates can evolve with different speeds. Breaking changes in an upstream crate don't mean you have migrate downstream to it right away, while with modules breaking changes in one module mandate migration of other modules as well.
  • Rustdoc generates nice docs useful for new team members.

Of course, semvering APIs can be an annoyance (especially during early development stages) and migration of downstream after breaking changes can take longer compared to the monolith approach. If you are not sure, you can start with modules and then gradually split them into crates after API has more or less stabilized.

3 Likes

Slightly veering off-topic: other people more knowledgeable than I could clarify, but looking at Tokio's Cargo.toml sure makes me think it is made of multiple crates.

It uses various crates such as num_cpus to do "awkward" low-level things that depend on the operating system, but I would describe it as a single crate apart from the tokio-macros crate. The clue is in the names!

1 Like

Thanks for your answers. At least, one topic is clear to me: seems a good idea to at least separate API and Core, and moreover, if code is not real business for the core crate, take it out and make it standalone.