The Cargo Guide
Core Cargo Command-Line Workflow
What the Core Cargo Workflow Is
Cargo's core command-line workflow is the set of commands you use repeatedly while developing, checking, running, testing, documenting, benchmarking, and cleaning a Rust package or workspace.
The central commands in this guide are:
cargo buildcargo checkcargo runcargo testcargo benchcargo doccargo clean
A good mental model is that Cargo is not just a builder. It is the command-line front door for the day-to-day lifecycle of a Rust project.
That means beginners should not think only in terms of "compile the code." They should think in terms of:
- validate quickly
- run the program
- test behavior
- inspect docs
- measure performance
- manage build artifacts
A Small Package We Can Use Throughout
Assume a small package like this:
cargo new workflow_demo
cd workflow_demoGenerated layout:
workflow_demo/
āāā Cargo.toml
āāā src/
āāā main.rsThen expand it slightly:
// src/lib.rs
pub fn square(x: i32) -> i32 {
x * x
}// src/main.rs
fn main() {
println!("{}", workflow_demo::square(8));
}// tests/basic.rs
use workflow_demo::square;
#[test]
fn squares_correctly() {
assert_eq!(square(5), 25);
}This kind of package is enough to make Cargo's core commands concrete.
cargo build
cargo build compiles the package and its dependencies.
cargo buildFor a basic package, this usually compiles in debug mode and places artifacts in target/debug/.
Typical beginner use:
cargo buildTypical result conceptually:
target/
āāā debug/
āāā workflow_demo
āāā deps/
āāā incremental/
āāā build/Use cargo build when you want actual compiled output but do not necessarily want to run the program immediately.
A useful distinction:
cargo buildproduces build artifactscargo checkvalidates code much faster without fully producing the same final artifacts
cargo check
cargo check type-checks and validates the code without performing a full normal build for execution.
cargo checkThis is one of the most important commands in Rust development because it gives fast feedback.
For example, suppose src/lib.rs contains an error:
pub fn square(x: i32) -> i32 {
x * "oops"
}Then:
cargo checkCargo will report a compiler error quickly, without waiting for a full executable build cycle.
A strong workflow habit is:
- use
cargo checkfrequently while editing - use
cargo buildwhen you want build output - use
cargo runwhen you want to execute the program
This is one of the most effective mental shifts for beginners.
cargo run
cargo run builds the selected binary target if needed and then runs it.
cargo runGiven this source:
fn main() {
println!("{}", workflow_demo::square(8));
}Running:
cargo runwill compile as needed and execute the default binary.
You can also pass arguments after --.
fn main() {
let args: Vec<String> = std::env::args().collect();
println!("args = {:?}", args);
}cargo run -- hello worldThat -- matters because it separates Cargo's own flags from the program's flags.
cargo test
cargo test builds and runs tests for the package.
cargo testWith this integration test:
use workflow_demo::square;
#[test]
fn squares_correctly() {
assert_eq!(square(5), 25);
}Cargo will compile the relevant targets and execute the test harness.
You can also filter tests by name:
cargo test squares_correctlyAnd you can pass arguments through to the test binary after --:
cargo test -- --nocaptureThis is useful when you want printed output from tests to be shown.
cargo bench
cargo bench runs benchmark targets.
cargo benchAt a workflow level, the important point is that benchmarks are a first-class Cargo activity just like builds, tests, and docs.
A minimal bench target might be declared and managed by Cargo even if the actual benchmarking strategy becomes more advanced later.
Illustrative layout:
workflow_demo/
āāā benches/
ā āāā perf.rs
āāā src/
ā āāā lib.rs
ā āāā main.rs
āāā Cargo.tomlIllustrative source:
fn main() {
println!("benchmark placeholder");
}The beginner takeaway is that Cargo's command surface extends beyond compilation and testing into performance-oriented workflows too.
cargo doc
cargo doc builds package documentation.
cargo docFor example, with library code:
/// Returns the square of a number.
pub fn square(x: i32) -> i32 {
x * x
}Then:
cargo docbuilds documentation into the target/doc/ area.
A very common command is:
cargo doc --openThat opens the generated documentation in a browser after building it.
This helps learners see that Cargo manages documentation generation as part of the normal package lifecycle.
cargo clean
cargo clean removes build artifacts from the target directory.
cargo cleanThis is useful when:
- you want to reclaim disk space
- you want to force a fresh build
- you are debugging stale artifact issues
- you want to understand which files Cargo regenerates
After cleaning, the next build will have to recreate the removed artifacts.
A simple workflow contrast:
cargo build
cargo clean
cargo buildThe second build will usually take longer than an incremental rebuild because the prior build outputs are gone.
Debug vs Release
Cargo uses different build profiles, with debug mode as the normal development default and release mode for optimized builds.
Default build:
cargo buildOptimized release build:
cargo build --releaseLikewise:
cargo run --release
cargo test --release
cargo doc --releaseIn general:
- debug builds favor faster compile times and development feedback
- release builds favor runtime performance
Artifacts are typically separated like this:
target/
āāā debug/
āāā release/Beginners should usually spend most of their day in debug mode and move to release mode when they care about final runtime behavior or performance.
What Goes Into target/
Cargo places build-related outputs under the target/ directory.
A typical shape looks like this:
target/
āāā debug/
ā āāā workflow_demo
ā āāā deps/
ā āāā incremental/
ā āāā build/
āāā release/
āāā doc/
āāā tmp/The exact contents vary, but a useful beginner mental model is:
target/debug/: development build artifactstarget/release/: optimized build artifactstarget/doc/: generated documentationtarget/debug/deps/: dependency artifactstarget/debug/incremental/: incremental compilation state
The target directory is Cargo's working artifact space. It is generated output, not source code.
Incremental Compilation
Incremental compilation means Cargo and the compiler reuse prior work when only part of the code has changed.
Typical experience:
cargo build
# first build may take longer
cargo build
# second build is often much faster if little changedNow change just one function:
pub fn square(x: i32) -> i32 {
x * x + 0
}Then:
cargo buildCargo can often reuse much of the earlier compilation work.
This is why repeated edit-build-check cycles are practical. It also explains why cargo clean removes more than just one executable: it removes the artifact history Cargo uses to speed up later builds.
Command Flags Change Behavior
Cargo subcommands share many behavioral patterns through flags.
Common examples:
cargo build --release
cargo test --release
cargo run --releaseTarget selection flags:
cargo run --bin admin_tool
cargo test --test api_checks
cargo run --example quickstart
cargo bench --bench perfFeature flags:
cargo build --features serde
cargo test --all-features
cargo run --no-default-featuresVerbosity flags:
cargo build -v
cargo build -vvA useful learning principle is that Cargo has strong cross-command conventions. Once you understand a few patterns, many subcommands start to feel similar.
Package Selection in Workspaces
In a workspace, Cargo commands can target one package, several packages, or all packages.
Example layout:
my_workspace/
āāā Cargo.toml
āāā app/
ā āāā Cargo.toml
ā āāā src/main.rs
āāā core/
āāā Cargo.toml
āāā src/lib.rsTop-level manifest:
[workspace]
members = ["app", "core"]
resolver = "2"Then from the workspace root:
cargo build
cargo build -p app
cargo test -p core
cargo check --workspaceImportant beginner idea:
- without package selection, the current manifest context matters
- in a workspace,
-plets you target one member explicitly
Target Selection Across Subcommands
Cargo lets you select target kinds explicitly.
Examples:
cargo run --bin workflow_demo
cargo run --example demo
cargo test --test basic
cargo bench --bench perfSuppose the package has this layout:
workflow_demo/
āāā src/
ā āāā lib.rs
ā āāā main.rs
ā āāā bin/
ā āāā admin.rs
āāā examples/
ā āāā demo.rs
āāā tests/
ā āāā basic.rs
āāā benches/
āāā perf.rsThen each command can narrow to a specific target rather than acting on everything of that kind.
Message Formats and Machine-Friendly Output
Cargo can emit output in formats that are useful for tools, editors, and automation.
For normal humans, default output is often enough:
cargo checkFor tool-oriented workflows, you may see options like message-format changes used in scripting and integrations.
Illustrative example:
cargo build --message-format=jsonThe exact downstream use depends on the tool consuming the output, but the key concept is that Cargo is not only a manual developer CLI. It is also an orchestration layer for machines and editor tooling.
Timing and Diagnostics-Oriented Usage
Cargo commands can be used not just to succeed or fail, but to learn something about the build.
For example, verbose output can help you understand what Cargo is doing:
cargo build -vHigher verbosity can expose even more detail:
cargo build -vvWhen diagnosing issues, developers often use combinations like:
cargo check -v
cargo test -- --nocapture
cargo build --release -vThe important learning point is that Cargo is not a black box. Its command-line surface supports investigation as well as execution.
Command Conventions Across Subcommands
Cargo's command set is large, but it becomes easier once you see the recurring conventions.
Some patterns repeat across many subcommands:
--releasechanges profile-pselects a package--featuresenables features--all-featuresenables all features--no-default-featuresdisables default features-vincreases verbosity--targetselects a compilation target platform--passes remaining arguments through to the executed binary or test harness where appropriate
This means Cargo is easier to learn as a system than as a bag of unrelated commands.
For example:
cargo build --release -p core
cargo test -p app --features cli
cargo run --bin admin --releaseRelevant Exit Codes in Tooling and CI
Cargo commands generally communicate success or failure through their process exit status.
At a practical level:
- successful command execution returns success
- build failures, test failures, and command errors return failure
This is why Cargo works cleanly in shell scripts and CI systems.
Simple shell example:
cargo check && cargo test && cargo docThat chain only continues while each command succeeds.
A CI-style script might look like this:
#!/usr/bin/env sh
set -e
cargo check
cargo test
cargo build --releaseBecause nonzero exit status signals failure, Cargo integrates naturally into automation.
The beginner takeaway is not to memorize exact numeric codes, but to understand that Cargo is script-friendly because command success and failure are reliable process-level signals.
Cargo in a Small Continuous Workflow
A realistic daily loop often looks like this:
- edit code
- run
cargo check - run
cargo test - run
cargo run - occasionally run
cargo doc --open - use
cargo build --releasewhen checking optimized behavior
For example:
cargo check
cargo test
cargo runThen after performance-sensitive changes:
cargo build --release
cargo run --releaseAnd when docs matter:
cargo doc --openThis sequence teaches Cargo as a development rhythm rather than a static list of commands.
A More Complete Package Example
Suppose the package grows into this:
workflow_demo/
āāā Cargo.toml
āāā src/
ā āāā lib.rs
ā āāā main.rs
ā āāā bin/
ā āāā admin.rs
āāā examples/
ā āāā quickstart.rs
āāā tests/
ā āāā basic.rs
āāā benches/
āāā perf.rsLibrary:
pub fn square(x: i32) -> i32 {
x * x
}Main binary:
fn main() {
println!("main: {}", workflow_demo::square(3));
}Additional binary:
fn main() {
println!("admin: {}", workflow_demo::square(4));
}Example:
use workflow_demo::square;
fn main() {
println!("example: {}", square(6));
}Integration test:
use workflow_demo::square;
#[test]
fn squares_correctly() {
assert_eq!(square(9), 81);
}Useful commands now include:
cargo check
cargo build
cargo run
cargo run --bin admin
cargo run --example quickstart
cargo test
cargo doc --openThat is already a substantial command-line workflow surface from one package.
Common Beginner Mistakes
Mistake 1: using cargo build for every tiny edit.
Often cargo check is the faster and better first step.
Mistake 2: forgetting that cargo run builds before running.
You do not usually need cargo build first unless you specifically want the artifact.
Mistake 3: confusing Cargo arguments with program arguments.
Remember the separator:
cargo run -- --my-program-flagMistake 4: expecting target/ to be source-controlled project content.
It is generated build output.
Mistake 5: forgetting workspace and package selection.
In multi-package repositories, -p often matters.
Mistake 6: assuming Cargo is only for builds.
It also drives tests, docs, benchmarks, and automation-friendly workflows.
Hands-On Exercise
Create a package and walk through the core workflow deliberately.
Start here:
cargo new cargo_flow_lab
cd cargo_flow_labAdd a library:
// src/lib.rs
pub fn cube(x: i32) -> i32 {
x * x * x
}Update main:
// src/main.rs
fn main() {
println!("{}", cargo_flow_lab::cube(3));
}Add a test:
// tests/basic.rs
use cargo_flow_lab::cube;
#[test]
fn cubes_correctly() {
assert_eq!(cube(4), 64);
}Now run these in order:
cargo check
cargo build
cargo run
cargo test
cargo doc --openThen inspect the target directory:
find target -maxdepth 2 -type d | sortThen try a release build:
cargo build --release
cargo run --releaseFinally clean up:
cargo cleanThis exercise helps connect commands, artifacts, and workflow habits into one coherent model.
Mental Model Summary
A strong mental model for Cargo's core command-line workflow is:
cargo checkgives fast correctness feedbackcargo buildproduces build artifactscargo runbuilds and executes a binary targetcargo testruns test targets through the test workflowcargo benchsupports benchmarking workflowscargo docbuilds documentationcargo cleanremoves generated artifactstarget/is Cargo's artifact workspace- debug and release modes serve different purposes
- shared flags and selection patterns make Cargo feel consistent across subcommands
- reliable exit status makes Cargo easy to use in scripts and CI
Once this model is stable, Cargo stops feeling like a list of commands and starts feeling like a coherent development operating system for Rust projects.
