C.W.K.
Stream
Lesson 03 of 06 · published

Workspaces

~10 min · tooling, workspaces, monorepo

Level 0Rust Curious
0 XP0/80 lessons0/19 achievements
0/100 XP to next level100 XP to go0% complete

When one package isn't enough — a project with a library, a CLI, and shared internal crates — you reach for a workspace: multiple related packages built and versioned together.

What a workspace is

A workspace is a top-level Cargo.toml with a [workspace] section listing member packages. They share one Cargo.lock and one target/ build directory, so dependencies are resolved once and compiled artifacts are shared. cargo build at the root builds every member.

Why split into a workspace

Splitting a large project into focused crates within a workspace gives you faster incremental builds (only changed crates recompile), clear internal boundaries (a crate's pub API is its contract to its siblings), and the ability to publish some crates while keeping others private. It's how non-trivial Rust projects are organized.

Workspaces are the real-world project shape. A typical app is a workspace: a core library crate, a binary crate that runs it, maybe a shared types crate, a macros crate. Cinder's native side is organized this way — a monorepo workspace with the Tauri app crate and supporting crates, sharing one lockfile and build dir. The module system scales up into the crate system, which scales up into the workspace.

Modules within, crates across

The mental model: use modules to organize code within a crate, and crates (within a workspace) to organize across compilation and publication boundaries. A crate that grows too large to reason about is the signal to split it; a tangle of tightly-coupled crates is the signal you split too early. Balance is the skill.

Code

A workspace with three member crates·toml
# Top-level Cargo.toml of a workspace
[workspace]
resolver = "2"
members = [
    "core",      # a library crate: the shared logic
    "cli",       # a binary crate: depends on core
    "protocol",  # a shared types crate
]

# cli/Cargo.toml depends on the sibling crate by path:
#   [dependencies]
#   core = { path = "../core" }

External links

Exercise

Sketch (on paper or in a scratch workspace) a 3-crate layout for a small app: a core library, a cli binary that depends on it, and a shared-types crate both use. Write the workspace Cargo.toml members list and the path dependency in cli. Why is this better than cramming everything into one crate?
Hint
Separate crates give faster incremental builds, enforced boundaries (core's pub API is its contract), and independent publishability. One giant crate recompiles entirely on any change and blurs internal boundaries.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.