17.1 Packages: The Top-Level Unit

17.1.1 What Is a Package?

A package is a collection of Rust crates that provides a set of functionality. It can contain multiple binary crates and optionally one library crate. The structure of a package is defined by a Cargo.toml file, which contains metadata about the package, such as its name, version, authors, and dependencies.

The Cargo command cargo new my_package creates a new package containing one binary crate, with the following file structure:

$ cargo new my_package
     Created binary (application) `my_package` package

$ tree my_package/
my_package/
├── Cargo.toml
└── src
    └── main.rs

2 directories, 2 files

Alternatively, we can create a library package by specifying the --lib flag:

$ cargo new my_rust_lib --lib
     Created library `my_rust_lib` package

$ cd my_rust_lib/
$ tree
.
├── Cargo.toml
└── src
    └── lib.rs

2 directories, 2 files

17.1.2 Components of a Package

A typical Rust package includes:

  • Cargo.toml: The manifest file containing package metadata, dependencies, and build configuration.
  • src/: The source code directory, which includes the crate roots (main.rs or lib.rs) and optionally additional module files or folders.
  • Cargo.lock: A lockfile that records the exact versions of dependencies used, ensuring consistent builds.
  • Tests and Documentation: Optional directories like tests/, examples/, and docs/ for integration tests, example code, and additional documentation.

Example Cargo.toml:

[package]
name = "my_package"
version = "0.1.0"
authors = ["Author Name <author@example.com>"]
edition = "2021"

[dependencies]
rand = "0.8"

When we build a binary package with the command cargo build, a target directory is created, which contains debug and release folders containing the executable file and other artifacts.

17.1.3 Workspaces: Managing Multiple Packages

For very large software projects that might contain multiple related packages developed closely together, workspaces can be used. Workspaces share a common Cargo.lock and output directory (target/), which simplifies dependency management and improves compilation times.

Example Workspace Layout:

my_workspace/
├── Cargo.toml
├── package_a/
│   ├── Cargo.toml
│   └── src/
│       └── lib.rs
└── package_b/
    ├── Cargo.toml
    └── src/
        └── main.rs

Workspace-level Cargo.toml:

[workspace]
members = ["package_a", "package_b"]

17.1.4 Packages with Multiple Binary Crates

A single package can contain additional binary crates, created by placing their Rust files in the src/bin/ directory. Each file corresponds to a separate binary crate that can be built and run independently.

Example Structure:

my_package/
├── Cargo.toml
└── src/
    ├── main.rs      // Primary binary crate
    └── bin/
        ├── tool.rs  // Additional binary crate
        └── helper.rs

You can build and run these binaries using Cargo commands:

  • Build all binaries: cargo build --bins
  • Run a specific binary: cargo run --bin tool

For more details, consult the Cargo Book.

17.1.5 Relationship Between Packages and Crates

In Rust:

  • A crate is a compilation unit; the compiler processes each crate as a whole.
  • A package is a collection of crates that are built and managed together.

A package can contain:

  • One library crate (optional).
  • Any number of binary crates (including none).

For a package with a single crate, the package and crate appear identical. However, understanding the distinction is important when working with more complex projects.