17.1 Packages: Bundling Crates with Cargo
A package is the fundamental unit Cargo works with. It represents a Rust project, containing the source code, configuration, dependencies, and metadata necessary to build one or more crates. Every package is defined by its Cargo.toml
manifest file located at the package root.
17.1.1 Creating a New Package
Cargo provides convenient commands to initialize a new package structure:
# Create a new package for a binary executable
cargo new my_executable_project
# Create a new package for a library
cargo new my_library_project --lib
For a binary package my_executable_project
, Cargo generates:
my_executable_project/
├── Cargo.toml # Package manifest
└── src/
└── main.rs # Crate root for the primary binary crate
For a library package my_library_project
, it generates:
my_library_project/
├── Cargo.toml # Package manifest
└── src/
└── lib.rs # Crate root for the library crate
17.1.2 Anatomy of a Package
A typical Rust package consists of:
Cargo.toml
: The manifest file. It contains metadata (name, version, authors, license), lists dependencies on other packages (crates), and specifies various package settings (features, build targets, etc.).src/
: The directory containing the source code.- It must contain at least one crate root:
src/main.rs
for the main binary crate orsrc/lib.rs
for the library crate. - It can contain other source files organized into modules (see Section 17.3).
- It may contain
src/bin/
for additional binary crates (see Section 17.1.4).
- It must contain at least one crate root:
Cargo.lock
: An automatically generated file recording the exact versions of all dependencies resolved during a build. This ensures reproducible builds. It’s recommended to commitCargo.lock
for binary packages but often excluded (.gitignore
) for library packages to allow downstream users flexibility in version resolution (though practices vary).- Optional Directories:
tests/
: For integration tests (each file is treated as a separate crate).examples/
: For example programs demonstrating the library’s usage (each file is a separate binary crate).benches/
: For benchmark code (each file is compiled like a test).
target/
: A directory created by Cargo during builds. It stores intermediate compilation artifacts and the final executables or libraries, typically organized intodebug/
andrelease/
subdirectories. This directory should always be excluded from version control.
17.1.3 Workspaces: Managing Multiple Packages
For larger projects involving several interdependent packages, Cargo offers workspaces. A workspace allows multiple packages to share a single Cargo.lock
file (ensuring consistent dependency versions across the workspace) and a common target/
build directory (potentially speeding up compilation by sharing compiled dependencies).
A workspace is defined by a root Cargo.toml
that designates member packages. The member packages still have their own individual Cargo.toml
files for package-specific metadata and dependencies.
my_workspace/
├── Cargo.toml # Workspace manifest (defines members)
├── package_a/ # Member package (e.g., a library)
│ ├── Cargo.toml
│ └── src/
│ └── lib.rs
└── package_b/ # Member package (e.g., a binary depending on package_a)
├── Cargo.toml
└── src/
└── main.rs
The root Cargo.toml
(in my_workspace/
) specifies the members:
[workspace]
members = [
"package_a",
"package_b",
# Can also use glob patterns like "crates/*"
]
# Optional: Define shared profile settings for all members
# [profile.release]
# opt-level = 3
# Note: Dependencies defined here are NOT automatically inherited by members.
# Each member package lists its own dependencies in its own Cargo.toml.
# However, a [workspace.dependencies] table can define shared versions
# that members can inherit explicitly.
Running cargo build
, cargo test
, etc., from the workspace root (my_workspace/
) will operate on all member packages.
17.1.4 Multiple Binaries within a Package
A single package can produce multiple executables.
- The file
src/main.rs
defines the primary binary crate, which typically shares the package name. - Any
.rs
file placed inside thesrc/bin/
directory defines an additional binary crate. Each file is compiled into a separate executable named after the file (e.g.,src/bin/tool_a.rs
compiles to an executable namedtool_a
).
my_package/
├── Cargo.toml
└── src/
├── main.rs # Compiles to 'my_package' executable
└── bin/
├── cli_tool.rs # Compiles to 'cli_tool' executable
└── server.rs # Compiles to 'server' executable
- Build all binaries:
cargo build
(orcargo build --bins
) - Build a specific binary:
cargo build --bin cli_tool
- Run a specific binary:
cargo run --bin cli_tool
This structure is useful for packaging a collection of related tools together. Both src/main.rs
and the files in src/bin/
can share code from src/lib.rs
if it exists in the same package.
17.1.5 Distinguishing Packages and Crates
It’s crucial to understand the distinction:
- A crate is a single unit of compilation, resulting in one library or one executable.
- A package is a unit managed by Cargo, defined by
Cargo.toml
. It contains the source code and configuration to build one or more crates.
Specifically, a single package can contain:
- Zero or one library crate (whose root is
src/lib.rs
). A package cannot have more than one library crate defined this way. - Any number of binary crates (defined by
src/main.rs
and files insrc/bin/
).
In simple projects with only src/main.rs
or src/lib.rs
, the package effectively contains just one crate. The distinction becomes important in larger projects, libraries with associated binaries, or workspaces where Cargo orchestrates the building of packages which, in turn, produce compiled crates.