Cargo Basics

Cargo is Rust’s project tool: it creates packages, builds crates, runs binaries, runs tests, downloads dependencies, and records exact dependency versions for reproducible builds.

What it is

Cargo combines the jobs that many ecosystems split across several tools. It is the default build system, package manager, test runner, dependency downloader, documentation builder, and convention enforcer for Rust projects.

Small single-file programs can be compiled with rustc, but normal Rust work starts with Cargo. The Book introduces Cargo immediately after “Hello, world!” because dependencies, multiple files, release builds, tests, and project metadata become easier when one tool owns the package model.

How it works

cargo new hello_cargo creates a package directory with Cargo.toml and src/main.rs. Cargo.toml describes package metadata, edition, dependencies, targets, and other configuration. Source code lives under src/; build outputs go under target/ rather than beside your source files.

Cargo reads the manifest, resolves dependencies from the configured registry, compiles dependency crates, compiles your crate, and stores build artifacts in profile-specific directories such as target/debug and target/release. The first dependency-resolving build creates Cargo.lock, which records exact versions. You normally edit Cargo.toml Manifest, not the lockfile by hand.

Cargo’s model has a few names worth separating early. A package is the directory described by one manifest. A package can contain one library crate, zero or more binary crates, examples, tests, and benches. A crate is a compilation unit. Cargo discovers many targets by conventional paths, then invokes rustc with the right crate roots, dependency search paths, profile settings, features, and edition.

On edition 2024 projects generated by current Cargo, the manifest explicitly contains edition = "2024". That also means resolver version 3 is the default for the package or workspace, so dependency resolution prefers versions compatible with declared rust-version when possible.

Example

fn main() {
    let package = "hello_cargo";
    println!("Cargo can build and run the {package} package.");
}

Worked example

The smallest binary package has a manifest and a crate root:

hello_cargo/
  Cargo.toml
  src/
    main.rs
[package]
name = "hello_cargo"
version = "0.1.0"
edition = "2024"
 
[dependencies]

cargo run reads the manifest, selects the binary target at src/main.rs, builds in the dev profile, writes artifacts under target/debug, and runs the resulting executable. If the package later gains a dependency, Cargo resolves it from the configured registry, records the exact version in Cargo.lock, and compiles dependency crates before compiling the local crate.

Common errors

Running Cargo outside a package gives a manifest-discovery error:

error: could not find `Cargo.toml` in current directory or any parent directory

Change into the package root, pass --manifest-path path/to/Cargo.toml, or create a package with Start Projects with cargo new.

When a package has multiple binaries, cargo run may need target selection:

error: `cargo run` could not determine which binary to run

Use cargo run --bin name or set default-run = "name" in [package].

Best practice

  • ✅ Start new Rust projects with cargo new or cargo init; let the generated structure teach the default conventions.
  • ✅ Use cargo run while learning and for binary crates; it compiles stale code and then executes the resulting binary.
  • ✅ Use Cargo Build Run Check Test as the daily command vocabulary: cargo check, cargo build, cargo run, and cargo test.
  • ✅ Keep dependency declarations in Cargo.toml Manifest and let Cargo update Cargo.lock.
  • ✅ Treat Cargo.toml as source and target/ as disposable build output.
  • ✅ Use Cargo’s target flags (--bin, --lib, --example, --test) before writing custom scripts for ordinary package selection.
  • ✅ Learn package/crate/target vocabulary early; it makes compiler and Cargo diagnostics much easier to read.

Pitfalls

  • ⚠️ Assuming Cargo is only a dependency downloader. It also owns build profiles, target discovery, tests, docs, examples, and workspace conventions.
  • ⚠️ Running generated binaries from memory instead of using cargo run; the path under target/debug is platform-specific and easy to mistype.
  • ⚠️ Replacing Cargo with ad hoc shell scripts too early; that is often Using rustc Directly for Cargo-Sized Projects.
  • ⚠️ Editing generated files under target/ or committing them as source.
  • ⚠️ Forgetting -- when passing arguments to a binary through cargo run; arguments after -- go to your program, not Cargo.

See also

rustup and Installation · Anatomy of a Cargo Project · Cargo Build Run Check Test · Cargo.toml Manifest · Cargo.lock · Packages and Crates · Cargo Workspaces · Start Projects with cargo new · Tooling & Getting Started

Sources