Understanding Cargo Workspaces

Hi,
I'm new in Rust Programming and try to port my small Game Engine from C++.
I already began to read through the Rust Book and made some small demos and enjoy the language:grin:.
Now I make hard to understand Cargo Workspaces. As far as I understand it's a feature to work with multiple crates that share the same dependencies(and maybe depend on eath other).
I thought I could split up my project into crates like Input, Graphics.... but is it the intention to use Cargo Workspaces this way (too)? Or am I misunderstanding the whole concept? Until this chapter a crate (for my interpretation) was a binary or library unit and not a part of a project.
Please enlighten me

Cargo workspaces are a way to bundle a few crates together like you said.
It would make more sense if each crate was reusable for other projects and not just one project.
My personal opinion for you would be to make a single crate for your project, and split graphics/input into separate modules - unless each part is generic and can be used easily in other projects.

Yes, cargo workspaces allow sharing dependencies between crates (workspace members) but only if the manifest of each crate allows it. If i have crate A and Crate B, which both require

bytes = ">0.0.1, <=0.1.0"

Cargo will select the latest constraint compatible version for both crate A and B, bytes=0.1.0.

There is also exactly 1 build folder per workspace, which means that the 'bytes' dependency only needs to be built once. Each crate can consume the same artefact to built itself.
Unless of course there is a version collision and bytes=1.0.0 and bytes=0.1.0 are necessary, so both are built.

Crates of the same project are often stored within a workspace because they share the same dependencies, be it external (from crates.io) or one from the workspace itself. This results in lower compilation times because each crate isn't built separately, including the entire dependency-chain per crate.

Splitting up functionality into multiple crates, which become members of the same workspace, depends on the structure of your code. Yes, you can mix binary and library crates within the same workspace.

I personally use it to reduce compilation time; code that doesn't change often is extracted into a separate crate and put into the workspace. This both lowers the amount of code, from my current crate, that needs to be compiled and as long as the extracted code doesn't change it doesn't need recompilation as well.

As functionality grows, you make it:

  • a bunch of methods
  • a module
  • a crate in the workspace
  • a standalone crate

So if your graphics-handling code outgrows what you can manage with a module or two, you can graduate to be its own crate with its own modules.

I splitted the Code of my project into an independent "generic" part and in its platform-dependent backends/plugins which were loaded at runtime.
In Rust I think it would be the best to define an own crate for any Plugin which use the same interface as defined in the generic part of the engine. Everything unified in a Cargo workspace.

Thanks for the quick answers^^

If you decide to use workspaces, you can find some tips for them over on Workspace workflow tips, gotchas, or recommendations?

1 Like

Just a quick note on this: look into conditional compilation in Rust. As an example:

#[cfg(target_os = "macos")]
pub mod darwin;

#[cfg(target_os = "macos")]
pub mod platform {
    pub use super::darwin::*;
}

#[cfg(target_os = "windows")]
pub mod windows;

#[cfg(target_os = "windows")]
pub mod platform {
    pub use super::windows::*;
}

It's not a super pretty looking pattern in that file, but you can of course just import it cleanly if you're worried about that (pub use path_to_platform_abstraction::path::*).

I like to keep it in the same place (even in C++) to allow me to unit test modules distinctly. While unit testing isn't the be-all-end-all, it can catch a lot of silly bugs, and allows you to expand into fuzzing later (if you're so inclined).