Large private single project - workspace or not?

I've used workspaces before, but the primary benefit of reduced compile times doesn't seem to override the pain of versions of transient dependencies between crates in the same workspace (which somewhat ironically increases compilation time).

I've got another new greenfield project coming and I'm tempted to just do it all in one crate (with judicious use of mods). I'd imagine incremental compilation would help here?

What are your experiences between the two approaches?

With incremental compilation you shouldn't see much of a difference between one monolithic crate and a workspace with several small crates.

I normally try to stay within one crate when hacking on something. That way you are always using the same dependencies and same feature set, which is better for incremental compilation. Otherwise, if you do something like cd crate-a && cargo build && cd ../crate-b && cargo build you'll end up invalidating a good chunk of your cache.

I use cargo outdated to keep all of my dependencies using the same version to avoid compiling the same crate twice. You can also use tools like cargo graph to visually figure out which of your crates might be pulling in duplicated/unnecessary dependencies.

For one of the projects I'm doing at work we originally started off with one monolith, then split into lots of smaller sub-crates, and now I'm starting to merge crates with related functionality to simplify the API.

My situation might be different to yours in that some of those sub-crates are useful on their own, and other crates can only be used in certain environments (e.g. crate A runs on a desktop while crate B is compiled to WebAssembly so crate A can run it), so it makes sense to split things up.

1 Like

(I tend to do everything from the crate root)

I really wish Cargo workspaces would allow you to put version ranges into a properties file or inherit from the parent Cargo.toml (ala maven) :slight_smile:

I’m confused. Based on my reading of the Cargo Book and The Book, all members of the workspace share Cargo.lock which should mean that they share transient dependencies. Of course if one crate calls for A = 2.0.0 and another calls for A = 3.0.0 there’s no sharing there, but if all crates specify compatible versions then I think they should be getting the same dependencies.

https://doc.rust-lang.org/book/ch14-03-cargo-workspaces.html#depending-on-an-external-package-in-a-workspace

https://doc.rust-lang.org/cargo/reference/workspaces.html

Is that not what you want or am I misunderstanding?

I believe that's what they are referring to.

Additionally, with v2 of Cargo's dependency resolver there are situations where you compile the same version of the same crate multiple times if you use different feature sets at different times in the build process.

For example, my main crate might pull in serde = { version = "1", default-features = false } (so I can use it in a #[no_std] context), but I might also use a procedural macro that has serde = { version = "1", features = ["std"] }. In this case, cargo will need to compile serde twice.

This is a lot better than v1 of the resolver because compiling serde once using the union of the features could/would break my main crate, but it does mean you do more work.

1 Like

@J-M0 - exactly as @Michael-F-Bryan states.

1 Like

Gotcha, yeah I can see how that would be desirable.

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.