13.6Cargo in depth
You've used Cargo since lesson 0.8: cargo new, cargo run, cargo build, cargo add. Those are the daily verbs, and they're most of what you'll ever type. This lesson is a tour of the rest, the parts of Cargo worth knowing exist so you recognize them when a project uses them. We won't go deep; the goal is a map, not a manual.
Cargo.toml, the control panel
Every Cargo command reads Cargo.toml, the file cargo new created. It's the project's control panel, written in TOML (a simple configuration format). A typical one:
[package]
name = "my_tool"
version = "0.1.0"
edition = "2024"
[dependencies]
rand = "0.10.1"
The [package] table names the package, sets its version (semver, lesson 7.9), and picks the edition (lesson 0.12). The [dependencies] table lists the crates you depend on, each line maintained by cargo add. You rarely edit Cargo.toml by hand for dependencies; cargo add rand and cargo remove rand do it for you and pick compatible versions.
Build profiles
Lesson 0.10 drew the debug-versus-release distinction. Cargo formalizes it as profiles. cargo build uses the dev profile (fast to compile, unoptimized, with debug info and overflow checks, lesson 4.4); cargo build --release uses the release profile (slow to compile, fully optimized, no debug overhead). You can tune them in Cargo.toml if you ever need to:
[profile.release]
opt-level = 3 # optimization level (0-3); 3 is the release default
Most projects never touch this. The defaults are well chosen: develop on dev for fast edit-compile-run cycles, ship --release for speed. Knowing profiles exist matters mostly so the [profile.*] tables in someone else's Cargo.toml aren't a mystery.
Useful commands beyond run and build
A handful of commands earn a place in your vocabulary:
cargo check type-checks your code without producing a binary. It's much faster than cargo build because it skips code generation, so it's the command to run constantly while editing to catch errors early (lesson 3.6's "compile often," made cheap).
cargo test builds and runs your tests, the entire subject of chapter 14.
cargo doc --open builds your project's documentation as a website and opens it in a browser, generated from your doc comments (lesson 13.7).
cargo fmt reformats your code to the standard style (lesson 1.9's rustfmt), and cargo clippy runs the extra lints (lesson 0.11). Both are how the community keeps style and quality consistent without arguing about it.
cargo run --release runs the optimized build, useful when you want to see real performance or your program is too slow under the unoptimized dev profile.
Best practice
Keep cargo check running as you write (many editors run it for you via rust-analyzer, lesson 0.7), cargo clippy before you consider a change done, and cargo fmt before you commit. These three turn the compiler, the linter, and the formatter into a constant safety net rather than a final gate, which is the whole ergonomic promise of a single official toolchain.
Workspaces, in two paragraphs
When a project grows into several related packages (a library, a command-line front end, maybe a shared utilities crate), you can group them into a workspace: a top-level Cargo.toml with a [workspace] table listing member packages that share one Cargo.lock and one target/ build directory. Building or testing the workspace builds or tests all members together, and members can depend on each other by path.
You don't need workspaces for anything in this course, and most small projects never use them. They matter once a codebase outgrows a single package, and the reason to mention them now is just so the word, and the multi-package layout, isn't unfamiliar when you meet a large open-source Rust project organized this way.
Features, by name only
Crates can offer optional functionality behind named features, toggled in Cargo.toml (rand = { version = "0.10.1", features = ["small_rng"] }). Features let a crate ship extra capabilities that not every user wants, keeping compile times and binary size down for those who don't. You'll occasionally enable a feature when a crate's docs tell you to; designing your own features is well beyond this chapter. For now, just know that a features = [...] line in a dependency is opting into optional parts of that crate.
Quiz time
Question #1
What's the difference between cargo check and cargo build, and when would you reach for check?
Show solution
cargo check type-checks the code and reports errors without generating an executable; cargo build does the full compile including code generation and produces a binary. check is much faster, so you run it frequently while editing to catch errors quickly, and only build (or run) when you actually need to execute the program.
Question #2
Which Cargo profile does cargo build use by default, and which does cargo build --release use? What's the tradeoff?
Show solution
cargo build uses the dev profile (fast compile, unoptimized, debug info, overflow checks); cargo build --release uses the release profile (slow compile, fully optimized, no debug overhead). The tradeoff is compile speed and debuggability (dev) versus runtime speed (release). Develop on dev, ship release.
Question #3
What is Cargo.toml, and which command normally edits its [dependencies] section for you?
Show solution
Cargo.toml is the package's manifest/control panel: name, version, edition, dependencies, and profile settings, written in TOML. cargo add (and cargo remove) edits the [dependencies] section for you, choosing compatible versions, so you rarely edit dependency lines by hand.
Cargo can build your docs, but only if you write them. The next lesson covers documentation comments, Rust's /// and //!, and why the language's documentation culture is genuinely one of its best features.