| |

Rust 28 🦀 Packages, Crates, Workspaces, and Cargo.toml

A Rust project is not a single thing. It is a package, which contains one or more crates. Those crates are compiled from a tree of modules. When a project grows beyond a single package, a workspace manages multiple related packages that share a lockfile and an output directory. This chapter covers the hierarchy—package, crate, module—and the Cargo.toml file that declares all of it.

Key point: A package is a Cargo feature that bundles one or more crates. A crate is a tree of modules that produces a library or an executable. A workspace is a collection of packages that share a Cargo.lock and a target directory. The Cargo.toml file describes how the package is built, what it depends on, and how it relates to other packages.


Why the package, crate, and workspace hierarchy exists

The compilation unit problem. Rust compiles one crate at a time. The crate is the unit of compilation. A package is the unit of distribution and versioning. A workspace is the unit of coordination for multiple packages that evolve together. Each level serves a different purpose, and Cargo uses the distinction to decide what to compile, what to publish, and what to share.

The dependency sharing problem. A project with ten packages that all depend on serde should not download and compile serde ten times. A workspace shares a single Cargo.lock file, so every package uses the same version of every dependency. It also shares a single target directory, so a dependency compiled for one package is reused by the others.

The metadata duplication problem. Every package in a workspace needs a version, an edition, and an author. Writing them in every Cargo.toml is repetitive, and keeping them in sync is error-prone. Workspace inheritance lets the root Cargo.toml define the metadata once, and each member inherits it with .workspace = true.

The scale problem. A single package works for a small project. A monorepo with a library, a CLI, a server, and shared types needs structure. The workspace provides that structure without forcing everything into one package or one compilation unit.


a. Packages, crates, and the Cargo.toml manifest

A package is a Cargo feature that bundles one or more crates. It has a Cargo.toml file that describes how to build the crates. A package can contain at most one library crate and any number of binary crates.

A crate is a tree of modules that produces a single compilation unit. A library crate compiles to a .rlib or .so file and is used by other crates. A binary crate compiles to an executable and has a main function.

The src directory holds the source code. src/main.rs is the crate root of a binary crate with the same name as the package. src/lib.rs is the crate root of a library crate with the same name. Additional binary crates go in src/bin/.

The Cargo.toml file is the manifest. It has several sections:

SectionPurpose
[package]Metadata: name, version, edition, authors, license
[dependencies]Runtime dependencies
[dev-dependencies]Test and example dependencies
[build-dependencies]Build script dependencies
[features]Optional features
[lib] / [[bin]]Target configuration
[workspace]Workspace definition

The [package] section is required for a package. The name and version are required. The edition is recommended. The [dependencies] section lists the crates the package uses.

[package]
name = "myapp"
version = "0.1.0"
edition = "2021"

[dependencies]
serde = { version = "1", features = ["derive"] }
tokio = { version = "1", features = ["full"] }

A dependency can be specified by version, path, git, or a combination. A path dependency refers to another package in the filesystem.

[dependencies]
myapp-core = { path = "../core" }

A virtual manifest is a Cargo.toml with a [workspace] section but no [package] section. It defines a workspace without a root package. This is used when there is no primary package and all packages are organized in separate directories.


b. Workspaces: members, excludes, and shared configuration

A workspace is a collection of packages that share a Cargo.lock file, a target directory, and various settings such as profiles. Packages in a workspace are called workspace members.

The [workspace] table defines the workspace. The members key lists the packages that belong to it. The exclude key removes paths from the workspace.

[workspace]
members = ["crates/core", "crates/api", "crates/cli"]
exclude = ["crates/experimental"]
resolver = "2"

All path dependencies that reside in the workspace directory automatically become members. Additional members are listed with members, which supports globs like crates/*.

The default-members key sets which members are built when a specific package is not selected. This is useful in a virtual workspace with many packages, where building all of them by default would be slow.

[workspace]
members = ["crates/*"]
default-members = ["crates/cli"]

When in the workspace root and no package is selected, Cargo builds the default members. If default-members is not specified, Cargo builds all members in a virtual workspace.

The resolver key sets the dependency resolver version. Workspaces should use resolver = "2" for Rust 2021 and resolver = "3" for Rust 2024. The resolver determines how features are unified across the dependency graph. Without it, Cargo uses the version-1 resolver, which can unify features incorrectly across build and normal dependencies.

The root Cargo.toml can define shared metadata in [workspace.package] and shared dependencies in [workspace.dependencies]. Member packages inherit these with .workspace = true.

[workspace.package]
version = "0.1.0"
edition = "2021"
authors = ["Alice <alice@example.com>"]
license = "MIT"

[workspace.dependencies]
serde = { version = "1", features = ["derive"] }
tokio = { version = "1", features = ["full"] }

A member package inherits the metadata and dependencies:

[package]
name = "myapp-core"
version.workspace = true
edition.workspace = true
authors.workspace = true
license.workspace = true

[dependencies]
serde.workspace = true
tokio.workspace = true

The inheritance eliminates duplication. The version is defined once. The dependency versions are defined once. When a dependency is updated, the update happens in the workspace root, and every member gets the new version.


c. Package selection, locking, and publishing

In a workspace, package-related commands use the -p or --package flag to select a specific package, or --workspace to operate on all packages.

cargo build -p myapp-core
cargo test --workspace
cargo run -p myapp-cli

If neither flag is specified, Cargo uses the package in the current working directory. If the current directory is a virtual workspace root, Cargo uses the default members or all members.

The workspace shares a single Cargo.lock file at the workspace root. Every package in the workspace uses the same version of every dependency. The lockfile should be committed to version control for both binaries and libraries. For binaries, the lockfile ensures reproducible builds. For libraries, the consuming project’s lockfile wins, but the library’s own CI benefits from a committed lockfile.

The workspace shares a single target directory at the workspace root. This avoids recompiling dependencies for each package. When one package depends on another, the dependency is compiled once and reused.

When publishing packages in a workspace, the publish order matters. If myapp-cli depends on myapp-core, then myapp-core must be published first. Cargo does not automatically determine the order, so the publisher must establish it.

The [patch], [replace], and [profile.*] sections in Cargo.toml are only recognized in the root manifest. Member crates’ manifests ignore these sections. This means that a workspace root can override a dependency version or set a profile for all members, but a member cannot override them locally.


Complete Example Session

# ============================================
# PART 1: WORKSPACE ROOT (VIRTUAL MANIFEST)
# ============================================
# /Cargo.toml
[workspace]
members = ["crates/core", "crates/api", "crates/cli"]
resolver = "2"

[workspace.package]
version = "0.1.0"
edition = "2021"
authors = ["Alice <alice@example.com>"]
license = "MIT"

[workspace.dependencies]
serde = { version = "1", features = ["derive"] }
tokio = { version = "1", features = ["full"] }
anyhow = "1"
# ============================================
# PART 2: MEMBER PACKAGE
# ============================================
# /crates/core/Cargo.toml
[package]
name = "myapp-core"
version.workspace = true
edition.workspace = true
authors.workspace = true
license.workspace = true

[dependencies]
serde.workspace = true
anyhow.workspace = true
# ============================================
# PART 3: MEMBER WITH PATH DEPENDENCY
# ============================================
# /crates/api/Cargo.toml
[package]
name = "myapp-api"
version.workspace = true
edition.workspace = true

[dependencies]
myapp-core = { path = "../core" }
serde.workspace = true
tokio.workspace = true
# ============================================
# PART 4: BINARY PACKAGE
# ============================================
# /crates/cli/Cargo.toml
[package]
name = "myapp-cli"
version.workspace = true
edition.workspace = true

[dependencies]
myapp-core = { path = "../core" }
myapp-api = { path = "../api" }
anyhow.workspace = true
# ============================================
# PART 5: BUILDING THE WORKSPACE
# ============================================
cargo build --workspace
# Compiles all three packages
# Shared target directory at workspace root
# Shared Cargo.lock at workspace root
# ============================================
# PART 6: BUILDING ONE PACKAGE
# ============================================
cargo build -p myapp-core
# Compiles only myapp-core and its dependencies
# ============================================
# PART 7: RUNNING THE BINARY
# ============================================
cargo run -p myapp-cli
# Runs the binary in crates/cli
# ============================================
# PART 8: DEFAULT MEMBERS
# ============================================
# /Cargo.toml
[workspace]
members = ["crates/core", "crates/api", "crates/cli"]
default-members = ["crates/cli"]

# cargo build in root builds only cli
# ============================================
# PART 9: EXCLUDING A PATH
# ============================================
[workspace]
members = ["crates/*"]
exclude = ["crates/experimental"]
# experimental is not part of the workspace
# ============================================
# PART 10: PACKAGE WITH BOTH LIB AND BIN
# ============================================
# /crates/tool/Cargo.toml
[package]
name = "mytool"
version = "0.1.0"

[lib]
name = "mytool_lib"
path = "src/lib.rs"

[[bin]]
name = "mytool"
path = "src/main.rs"

The ten parts covered a virtual workspace root, a member package with inheritance, a member with a path dependency, a binary package, building the workspace, building one package, running the binary, default members, excluding a path, and a package with both a library and a binary.


Quick Reference

Cargo.toml Sections

SectionPurpose
[package]Package metadata
[dependencies]Runtime dependencies
[dev-dependencies]Test and example dependencies
[build-dependencies]Build script dependencies
[features]Optional features
[lib] / [[bin]]Target configuration
[workspace]Workspace definition
[workspace.package]Shared metadata
[workspace.dependencies]Shared dependencies

Workspace Keys

KeyPurpose
membersPackages in the workspace
excludePackages excluded
default-membersDefault packages to build
resolverDependency resolver version
packageShared metadata
dependenciesShared dependencies

Package Selection

CommandEffect
cargo build -p nameBuild one package
cargo build --workspaceBuild all packages
cargo run -p nameRun one binary
cargo test --workspaceTest all packages

Inheritance Syntax

MemberInherits From
version.workspace = true[workspace.package]
serde.workspace = true[workspace.dependencies]

Best Practices

✅ Do This:

# Use a virtual manifest for multi-package projects
[workspace]
members = ["crates/*"]
resolver = "2"                                              # ✅

# Share metadata in the workspace root
[workspace.package]
version = "0.1.0"
edition = "2021"                                            # ✅

# Share dependencies in the workspace root
[workspace.dependencies]
serde = { version = "1", features = ["derive"] }            # ✅

# Inherit in member packages
version.workspace = true
serde.workspace = true                                     # ✅

# Use default-members for a primary binary
default-members = ["crates/cli"]                           # ✅

# Commit Cargo.lock
# (for reproducible builds)                                # ✅

❌ Don’t Do This:

# Don't forget resolver in a virtual manifest
[workspace]
members = ["crates/*"]
# resolver missing                                           # ⚠️

# Don't duplicate dependency versions in every member
[dependencies]
serde = "1"  # in every member                              # ⚠️

# Don't use [package] in a virtual manifest
[workspace]
members = ["crates/*"]
[package]  # not allowed in virtual manifest                # ❌

# Don't define [patch] in a member package
[patch.crates-io]
# only recognized in the workspace root                    # ❌

# Don't publish dependent packages before their dependencies
# Publish myapp-core before myapp-cli                        # ⚠️

# Don't forget the path in path dependencies
myapp-core = { path = "core" }  # wrong relative path       # ❌

Common Pitfalls

PitfallWhy It HappensFix
Resolver warningMissing resolver in workspace rootAdd resolver = "2"
Member not foundNot listed in membersAdd to members
Path dependency not in workspaceexclude includes itRemove from exclude
Shared version not inheritedMissing .workspace = trueAdd the inheritance
Package builds everythingNo default-membersAdd default-members
Lockfile conflictsSeparate lockfilesUse one workspace root
Publish failsDependency not publishedPublish in order

Real-World Examples

1. Virtual Workspace

[workspace]
members = ["crates/*"]
resolver = "2"

2. Root Package Workspace

[package]
name = "myapp"
version = "0.1.0"

[workspace]
members = ["crates/*"]

3. Shared Metadata

[workspace.package]
version = "0.1.0"
edition = "2021"
license = "MIT"

4. Shared Dependencies

[workspace.dependencies]
serde = { version = "1", features = ["derive"] }
tokio = "1"

5. Member Inheritance

[package]
name = "core"
version.workspace = true

[dependencies]
serde.workspace = true

6. Path Dependency

[dependencies]
myapp-core = { path = "../core" }

7. Default Members

[workspace]
default-members = ["crates/cli"]

8. Exclude

[workspace]
members = ["crates/*"]
exclude = ["crates/experimental"]

9. Package Selection

cargo build -p myapp-core
cargo test --workspace

10. Lib and Bin in One Package

[lib]
name = "mytool_lib"

[[bin]]
name = "mytool"

Visual

Package, Crate, Module Hierarchy

┌─────────────────────────────────────────────────────────────┐
│  PACKAGE (Cargo.toml)                                       │
│  ├── LIBRARY CRATE (src/lib.rs)                             │
│  │   ├── mod models                                         │
│  │   ├── mod db                                             │
│  │   └── mod api                                            │
│  └── BINARY CRATE (src/main.rs)                             │
│      └── mod handlers                                       │
│                                                             │
│  Package: bundle of crates, described by Cargo.toml         │
│  Crate: compilation unit, tree of modules                   │
│  Module: namespace, unit of privacy                         │
│                                                             │
└─────────────────────────────────────────────────────────────┘

Workspace Structure

┌─────────────────────────────────────────────────────────────┐
│  workspace-root/                                            │
│  ├── Cargo.toml          (workspace definition)             │
│  ├── Cargo.lock          (shared lockfile)                  │
│  ├── target/             (shared build output)              │
│  └── crates/                                                │
│      ├── core/                                              │
│      │   ├── Cargo.toml                                     │
│      │   └── src/lib.rs                                     │
│      ├── api/                                               │
│      │   ├── Cargo.toml                                     │
│      │   └── src/lib.rs                                     │
│      └── cli/                                               │
│          ├── Cargo.toml                                     │
│          └── src/main.rs                                    │
│                                                             │
│  One lockfile. One target directory. Shared dependencies.   │
│                                                             │
└─────────────────────────────────────────────────────────────┘

Workspace Inheritance

┌─────────────────────────────────────────────────────────────┐
│  ROOT Cargo.toml                                            │
│  [workspace.package]                                        │
│  version = "0.1.0"                                          │
│  edition = "2021"                                           │
│                                                             │
│  [workspace.dependencies]                                   │
│  serde = { version = "1", features = ["derive"] }           │
│                                                             │
│         │                                                   │
│         ├──────────────────┬──────────────────┐             │
│         ▼                  ▼                  ▼             │
│  MEMBER A             MEMBER B             MEMBER C         │
│  version.workspace    version.workspace    version.workspace│
│  = true               = true               = true           │
│                                                             │
│  serde.workspace      serde.workspace      serde.workspace  │
│  = true               = true               = true           │
│                                                             │
│  Defined once in root. Inherited by all members.            │
│                                                             │
└─────────────────────────────────────────────────────────────┘

Package Selection

┌─────────────────────────────────────────────────────────────┐
│  cargo build -p myapp-core                                  │
│  Builds only myapp-core and its dependencies.               │
│                                                             │
│  cargo build --workspace                                    │
│  Builds all members.                                        │
│                                                             │
│  cargo build (in crates/cli/)                               │
│  Builds the package in the current directory.               │
│                                                             │
│  cargo build (in workspace root)                            │
│  Builds default-members, or all members if not set.         │
│                                                             │
└─────────────────────────────────────────────────────────────┘

Summary

ItemValue
PackageBundle of crates, described by Cargo.toml
CrateCompilation unit, tree of modules
Library cratesrc/lib.rs
Binary cratesrc/main.rs
WorkspaceCollection of packages
Virtual manifest[workspace] without [package]
MembersPackages in the workspace
Shared lockfileCargo.lock at workspace root
Shared targettarget/ at workspace root
Inheritance.workspace = true
Package selection-p name, --workspace
Resolverresolver = "2"

Key takeaways:

  • A package bundles one or more crates. It has a Cargo.toml that describes how to build them. A package can contain at most one library crate and any number of binary crates.
  • A crate is a tree of modules that compiles as a unit. The crate root is src/lib.rs for a library or src/main.rs for a binary. Modules organize the code within the crate.
  • A workspace is a collection of packages that share a lockfile and a target directory. All packages use the same version of every dependency and share the same build output. This avoids duplicate compilation and version drift.
  • A virtual manifest defines a workspace without a root package. It has a [workspace] section but no [package] section. It is used when there is no primary package.
  • Workspace inheritance eliminates duplication. The root Cargo.toml defines shared metadata in [workspace.package] and shared dependencies in [workspace.dependencies]. Member packages inherit them with .workspace = true.
  • The resolver key must be set in a virtual manifest. Without it, Cargo uses the version-1 resolver, which unifies features incorrectly across build and normal dependencies. Use resolver = "2" for Rust 2021.
  • Package selection uses -p or --workspace. In a workspace, cargo build -p name builds one package. cargo build --workspace builds all. In the workspace root, default-members determines what is built when no package is selected.
  • The publish order matters. If one package depends on another, the dependency must be published first. Cargo does not determine the order automatically.

Remember: The Rust project hierarchy has three levels: package, crate, and module. A package is the unit of distribution. A crate is the unit of compilation. A module is the unit of privacy. A workspace coordinates multiple packages that evolve together, sharing a lockfile and a target directory. The Cargo.toml file declares all of it. Use a workspace when a project grows beyond a single package. Use inheritance to avoid duplicating metadata and dependency versions. Use -p and --workspace to control which packages are built. The hierarchy is not complexity for its own sake—it is what allows a large Rust project to be organized, compiled, and released as a coherent whole.



Stop using slow, ad-bloated tool sites! 🤮

🔎 Search “KandZ Tools” on Google to use many professional utilities for free.

KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)

⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free

🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!