The Cargo Guide

Package Selection and Command Scoping

Why Package Selection and Command Scoping Matter

Once a repository contains more than one package or more than one target, Cargo commands stop meaning just "build this project". They begin to mean "build which package, from which manifest context, and for which targets?"

A useful mental model is:

  • package selection decides which package or packages Cargo operates on
  • target selection decides which targets inside those packages Cargo operates on
  • command scoping depends on where you invoke Cargo from and whether you override that with flags

The Three Main Scoping Layers

Cargo command scoping usually has three layers.

First, manifest scope:

  • which manifest Cargo is using

Second, package scope:

  • which package or workspace members are selected

Third, target scope:

  • which library, binary, example, test, or benchmark targets inside those packages are selected

Understanding all three layers makes commands like cargo build, cargo test, and cargo run much easier to reason about in multi-package repositories.

A Small Workspace Example

Suppose you have this workspace:

workspace_demo/
ā”œā”€ā”€ Cargo.toml
ā”œā”€ā”€ app/
│   ā”œā”€ā”€ Cargo.toml
│   └── src/main.rs
ā”œā”€ā”€ core/
│   ā”œā”€ā”€ Cargo.toml
│   └── src/lib.rs
└── tools/
    ā”œā”€ā”€ Cargo.toml
    └── src/main.rs

Root manifest:

[workspace]
members = ["app", "core", "tools"]
default-members = ["app", "core"]
resolver = "3"

This workspace is enough to demonstrate package selection and command scoping clearly.

Default Behavior Depends on the Selected Manifest

When you run a Cargo command without explicit package-selection flags, the default selected packages depend on the selected manifest.

A useful mental model is:

  • if the selected manifest is the root of a workspace, Cargo uses workspace selection rules
  • otherwise Cargo operates on the single package defined by that manifest

This is why current directory and --manifest-path matter so much.

Working from the Workspace Root

Suppose you run this from the workspace root:

cd workspace_demo
cargo build

Because the selected manifest is the workspace root, Cargo uses workspace package-selection behavior.

With this root manifest:

[workspace]
members = ["app", "core", "tools"]
default-members = ["app", "core"]
resolver = "3"

that plain cargo build targets app and core by default.

Working from a Member Subdirectory

Now suppose you run Cargo from inside a member directory:

cd workspace_demo/app
cargo build

In that case, the selected manifest is the app package manifest, so Cargo operates on that package by default.

This is one of the most important practical distinctions in Cargo command scoping: the same command text can select different packages depending on which manifest Cargo is using.

The -p or --package Flag

The -p and --package flags explicitly select one package.

Examples:

cargo build -p app
cargo test -p core
cargo doc --package tools

A useful mental model is:

  • -p overrides default package selection
  • it is especially important at workspace roots and in larger monorepos

This is often the cleanest way to make a command unambiguous.

Selecting All Workspace Members with --workspace

The --workspace flag selects all workspace members.

Examples:

cargo build --workspace
cargo test --workspace
cargo doc --workspace

This is useful when you want a full repository-wide action rather than the root defaults or one package.

In a virtual workspace without default-members, plain root invocation can already behave similarly to --workspace, but using --workspace makes your intent explicit.

Excluding Members with --exclude

The --exclude flag removes selected packages from a workspace-wide operation.

Examples:

cargo build --workspace --exclude tools
cargo test --workspace --exclude app

This is especially useful in large workspaces where one or more members are slow, experimental, platform-specific, or not relevant to the current task.

A good mental model is:

  • --workspace broadens selection to all members
  • --exclude trims that broad selection down

Why default-members Changes Root-Level Behavior

The default-members field lets a workspace choose which members root-level commands operate on when package-selection flags are not used.

Example:

[workspace]
members = ["app", "core", "tools"]
default-members = ["app", "core"]
resolver = "3"

Now these commands from the workspace root target only app and core by default:

cargo build
cargo test
cargo doc

This is a powerful way to keep common root-level commands focused in larger workspaces.

Root Package vs Virtual Workspace Behavior

There is an important difference between a virtual workspace and a workspace that also has a root package.

In a virtual workspace with no root package, root-level commands without package-selection flags default to all workspace members unless default-members is set.

In a non-virtual workspace with a root package, plain root-level commands default to the root package, and you use --workspace or --package to select other members.

This is one reason virtual workspaces often feel clearer in large monorepos.

Target Selection Inside a Package

Once Cargo has selected a package, target-selection flags decide which targets inside that package to operate on.

Common target-selection flags include:

  • --lib
  • --bin
  • --bins
  • --example
  • --examples
  • --test
  • --tests
  • --bench
  • --benches

These are especially important for commands like cargo build, cargo test, cargo bench, cargo doc, and cargo rustdoc.

Selecting a Binary Target

Suppose a package has this layout:

app/
ā”œā”€ā”€ Cargo.toml
└── src/
    ā”œā”€ā”€ main.rs
    └── bin/
        └── admin.rs

Now you can select binaries explicitly:

cargo build -p app --bin admin
cargo run -p app --bin admin
cargo build -p app --bins

A useful mental model is:

  • package selection chooses app
  • target selection chooses which binary inside app

Selecting Examples

Suppose a package has:

app/
ā”œā”€ā”€ examples/
│   ā”œā”€ā”€ quickstart.rs
│   └── demo.rs
└── Cargo.toml

You can select examples like this:

cargo run -p app --example quickstart
cargo build -p app --examples

Examples are part of the target-selection surface, not separate packages.

Selecting Tests and Benches

Suppose a package has these targets:

app/
ā”œā”€ā”€ tests/
│   └── api.rs
└── benches/
    └── perf.rs

You can scope commands to them explicitly:

cargo test -p app --test api
cargo test -p app --tests
cargo bench -p app --bench perf
cargo bench -p app --benches

This is useful when a package has many targets and you do not want the command to act on everything.

Glob Patterns for Target Selection

Several target-selection flags support common Unix glob patterns.

Examples:

cargo build --bin 'adm*'
cargo test --test 'api*'
cargo bench --bench 'perf*'

A practical note is that these patterns should be quoted so your shell does not expand them before Cargo sees them.

How cargo run Is Scoped

cargo run is a little more specific than commands like cargo build or cargo test because it runs one binary or example.

Examples:

cargo run -p app
cargo run -p app --bin admin
cargo run -p app --example quickstart

If the selected package has only one runnable binary target, cargo run can often infer it. If there are several, being explicit is usually better.

How cargo test Is Broader by Default

cargo test has broader default target behavior than cargo run.

When no target-selection flags are given, it may build and run several target categories for the selected packages, including unit tests, integration tests, examples for compilation checks, and doc tests.

That is why target-selection flags are especially useful when you want a narrower test action in a larger package.

Manifest Path Control with --manifest-path

The --manifest-path flag lets you tell Cargo exactly which manifest file to use.

Examples:

cargo build --manifest-path app/Cargo.toml
cargo test --manifest-path core/Cargo.toml
cargo doc --manifest-path Cargo.toml --workspace

This is one of the most important ways to control command scope programmatically or from scripts, because it decouples Cargo selection from the current working directory.

Why --manifest-path Is So Useful

--manifest-path is especially useful when:

  • scripts run from arbitrary directories
  • tooling needs to operate on a specific package without cd
  • monorepos have many possible Cargo entry points
  • you want command behavior to be explicit rather than location-dependent

A practical mental model is:

  • current directory is implicit manifest selection
  • --manifest-path is explicit manifest selection

Working from Subdirectories Intentionally

Cargo can often locate the relevant workspace context even when you run it from inside a member directory.

For example:

cd workspace_demo/core
cargo test

This usually selects the core package manifest, while still understanding that the package belongs to a surrounding workspace.

This makes subdirectory-based workflows convenient, but it also means that two developers running commands from different directories may unintentionally scope commands differently unless they are explicit.

A Concrete Scoping Comparison

Suppose the workspace root manifest is:

[workspace]
members = ["app", "core", "tools"]
default-members = ["app", "core"]
resolver = "3"

Then compare these:

cd workspace_demo
cargo build

This builds app and core.

cd workspace_demo/tools
cargo build

This builds only tools by default.

cd workspace_demo
cargo build -p tools

This builds only tools from the root.

The command text is similar, but the scope changes because the selected manifest context changes.

Selecting Packages and Targets Together

Package selection and target selection often work together.

Examples:

cargo build -p app --bin admin
cargo run -p app --example quickstart
cargo test -p core --lib
cargo bench -p app --bench perf

This is one of the most useful Cargo habits in larger repositories: specify both package and target when you want a command to be completely unambiguous.

Locating Projects Programmatically with cargo metadata

When tools need to discover the current package, workspace members, target layout, or resolved graph in a structured way, cargo metadata is the main command.

Example:

cargo metadata --format-version 1

This outputs JSON describing the workspace and package graph.

A useful mental model is:

  • cargo tree is for human-oriented graph inspection
  • cargo metadata is for tool-oriented project discovery and programmatic integration

Using cargo metadata with --manifest-path

You can combine project discovery with explicit manifest selection.

Example:

cargo metadata --format-version 1 --manifest-path app/Cargo.toml

This is useful for scripts, editor integrations, code generators, or custom tooling that need a stable way to discover Cargo project structure without depending on the current working directory.

A Small Programmatic Discovery Workflow

Suppose a tool wants to know which workspace it is in and which packages exist.

A stable shell-level approach is:

cargo metadata --format-version 1 --no-deps

And if the tool needs to anchor discovery to a specific manifest:

cargo metadata --format-version 1 --no-deps --manifest-path Cargo.toml

This is far more reliable than manually walking directories and guessing where workspaces begin.

A Full Multi-Package Example

Suppose you have this workspace:

workspace_demo/
ā”œā”€ā”€ Cargo.toml
ā”œā”€ā”€ app/
│   ā”œā”€ā”€ Cargo.toml
│   ā”œā”€ā”€ src/
│   │   ā”œā”€ā”€ main.rs
│   │   └── bin/
│   │       └── admin.rs
│   ā”œā”€ā”€ examples/
│   │   └── quickstart.rs
│   ā”œā”€ā”€ tests/
│   │   └── api.rs
│   └── benches/
│       └── perf.rs
ā”œā”€ā”€ core/
│   ā”œā”€ā”€ Cargo.toml
│   └── src/lib.rs
└── tools/
    ā”œā”€ā”€ Cargo.toml
    └── src/main.rs

Useful scoped commands from the root might include:

cargo build
cargo build --workspace
cargo build --workspace --exclude tools
cargo build -p app --bin admin
cargo run -p app --example quickstart
cargo test -p app --test api
cargo bench -p app --bench perf
cargo metadata --format-version 1 --no-deps

This is the kind of command surface where package selection and target selection become a normal part of everyday workflow.

Command Scoping in Scripts

In scripts, it is usually better to make scoping explicit.

Example:

#!/usr/bin/env sh
set -e
 
cargo build --manifest-path app/Cargo.toml
cargo test --manifest-path core/Cargo.toml
cargo build --manifest-path Cargo.toml --workspace --exclude tools

This is often more robust than relying on cd plus implicit root detection, especially in CI or automation.

Common Beginner Mistakes

Mistake 1: assuming cargo build means the same thing from every directory.

Mistake 2: forgetting that root package, virtual workspace, and default-members change root-level behavior.

Mistake 3: using target-selection flags without noticing which package was selected.

Mistake 4: using --workspace but forgetting that --exclude may be needed in large repos.

Mistake 5: writing scripts that depend on current working directory instead of using --manifest-path.

Mistake 6: trying to discover workspaces or packages programmatically without using cargo metadata.

Hands-On Exercise

Create a small workspace and practice command scoping deliberately.

Root Cargo.toml:

[workspace]
members = ["app", "core", "tools"]
default-members = ["app", "core"]
resolver = "3"

Then try these commands from the root:

cargo build
cargo build --workspace
cargo build --workspace --exclude tools
cargo build -p app
cargo metadata --format-version 1 --no-deps

Then from inside app/:

cargo build
cargo run --bin admin
cargo run --example quickstart
cargo test --test api

Then compare with explicit manifest control:

cargo build --manifest-path app/Cargo.toml
cargo test --manifest-path core/Cargo.toml

This exercise makes the interaction between manifest context, package selection, and target selection very clear.

Mental Model Summary

A strong mental model for package selection and command scoping in Cargo is:

  • Cargo first decides which manifest is selected
  • then it decides which package or packages are selected
  • then it decides which targets inside those packages are selected
  • -p selects a package explicitly
  • --workspace selects all workspace members
  • --exclude trims a workspace-wide selection
  • target flags like --bin, --example, --test, and --bench narrow the action inside a package
  • --manifest-path is the main way to make manifest scope explicit
  • cargo metadata is the main way to discover projects programmatically

Once this model is stable, Cargo commands in multi-package repositories become much easier to read as scoped operations rather than as magic.