RustCargo — The Build Tool

Cargo — The Build Tool

Cargo is Rust's official package manager and build system. It handles compiling your code, downloading libraries your project depends on, and building those libraries. Almost every Rust project uses Cargo — it is the standard tool for the entire ecosystem.

What Cargo does
  • Creates and manages project structure

  • Compiles your Rust code

  • Downloads and compiles dependencies from crates.io

  • Runs tests with cargo test

  • Generates documentation with cargo doc

  • Publishes libraries to crates.io with cargo publish

  • Runs linting with cargo clippy and formatting with cargo fmt

Creating a new project

Use cargo new to create a new project with a ready-to-go directory structure, or cargo init to initialize Cargo inside an existing directory.

Bash
# Create a new binary (executable) project
cargo new my_project

# Create a new library project
cargo new my_library --lib

# Initialize Cargo in the current directory
cargo init

# Initialize a library in the current directory
cargo init --lib

After running cargo new my_project, you get this structure:

Text
my_project/
├── Cargo.toml      ← package metadata and dependencies
├── Cargo.lock      ← exact dependency versions (auto-generated)
└── src/
    └── main.rs     ← entry point for binary projects
Note
For a library project the entry point is `src/lib.rs` instead of `src/main.rs`. A project can have both if it exposes a public API and also ships a CLI.
src/main.rs vs src/lib.rs

File

Purpose

When to use

src/main.rs

Entry point for an executable

CLI tools, applications, programs users run directly

src/lib.rs

Root of a library crate

Reusable code other projects import as a dependency

Both present

Binary + library in one package

CLI tool that also exposes a public API

Cargo.toml — package manifest

Cargo.toml is the heart of every Cargo project. It declares the package name, version, Rust edition, and all dependencies.

TOML
[package]
name = "my_project"
version = "0.1.0"
edition = "2021"
authors = ["Your Name <you@example.com>"]
description = "A short description of the project"
license = "MIT"
readme = "README.md"
homepage = "https://example.com"
repository = "https://github.com/you/my_project"
keywords = ["cli", "tool"]
categories = ["command-line-utilities"]

[dependencies]
serde = { version = "1", features = ["derive"] }
tokio = { version = "1", features = ["full"] }
reqwest = { version = "0.11", features = ["json"] }
anyhow = "1"

[dev-dependencies]
# Only compiled for tests and benchmarks
tempfile = "3"
mockall = "0.11"

[features]
# Optional feature flags
default = []
async-support = ["tokio"]

[[bin]]
# Optional: declare additional binaries in the same package
name = "my_tool"
path = "src/bin/my_tool.rs"
Declaring dependencies

Cargo fetches dependencies from crates.io, Rust's public package registry. You can also depend on Git repositories or local paths.

TOML
[dependencies]
# Simple version requirement
serde = "1"

# Explicit version with features
serde = { version = "1.0", features = ["derive"] }

# Minimum version (caret — compatible with 1.x)
tokio = "^1.0"

# Tilde — only patch updates (1.2.x)
log = "~1.2"

# Exact version
anyhow = "=1.0.68"

# From a Git repository
my_crate = { git = "https://github.com/user/my_crate" }

# From a Git repository at a specific branch or tag
my_crate = { git = "https://github.com/user/my_crate", branch = "main" }

# From a local path (useful for monorepos or local development)
utils = { path = "../utils" }

[dev-dependencies]
# These are only used when running tests or benchmarks
pretty_assertions = "1"
criterion = "0.5"
Semantic versioning in Cargo

Cargo uses Semantic Versioning (SemVer). The caret (^) is the default specifier and means "compatible with this version". For versions 1.0 and above, a caret allows any update that does not change the leftmost non-zero digit.

Specifier

Allows

Example

^1.2.3

=1.2.3 and <2.0.0

Any 1.x that is at least 1.2.3

^0.2.3

=0.2.3 and <0.3.0

Pre-1.0 — minor is breaking

~1.2.3

=1.2.3 and <1.3.0

Patch updates only

=1.2.3

Exactly 1.2.3

Pin to one exact version

Any version

Almost never use this

Cargo.lock — reproducible builds

Cargo.lock records the exact version of every dependency (and transitive dependency) that was resolved during the last build. This guarantees that every developer on the team, and every CI run, uses identical crate versions.

  • Commit Cargo.lock for binary projects (applications, CLI tools) — ensures reproducible builds across machines

  • Do NOT commit Cargo.lock for library crates — let downstream consumers choose compatible versions

  • Never edit Cargo.lock manually — Cargo manages it automatically

  • Run cargo update to refresh Cargo.lock to the latest compatible versions

Essential Cargo commands

Command

What it does

cargo build

Compile the project (debug mode)

cargo build --release

Compile with full optimisations (slower compile, fast binary)

cargo run

Build and immediately run the binary

cargo run -- --flag arg

Pass arguments to the binary after --

cargo check

Type-check without producing a binary (very fast)

cargo test

Compile and run all tests

cargo test my_fn

Run only tests whose name contains "my_fn"

cargo clippy

Run the Clippy linter for idiomatic warnings

cargo fmt

Format all source files with rustfmt

cargo doc --open

Build HTML docs and open in browser

cargo add serde

Add a dependency (requires cargo-edit plugin)

cargo update

Update Cargo.lock to latest compatible versions

cargo clean

Delete the target/ build directory

cargo publish

Publish this crate to crates.io

cargo check — the fastest feedback loop

cargo check is the command you will use most while writing code. It type-checks your entire project and reports errors without spending time generating a binary. It is typically 2–5× faster than cargo build.

Bash
# Fast: only checks types, no binary produced
cargo check

# Slower: full compile, produces a binary in target/debug/
cargo build

# Fastest runtime, slowest compile: optimised binary in target/release/
cargo build --release
Tip
During development, keep a terminal running `cargo check` (or install `cargo-watch` and run `cargo watch -x check`) so errors appear instantly as you save files.
Build profiles — dev vs release

Cargo has two built-in build profiles that control optimisation level, debug symbols, and overflow checks.

Profile

Command

Opt level

Debug info

Best for

dev

cargo build

0 (none)

Full

Fast iteration during development

release

cargo build --release

3 (max)

Stripped

Shipping to production or benchmarking

You can customise profiles in Cargo.toml:

TOML
[profile.dev]
opt-level = 0          # no optimisation, fast compile
debug = true           # include debug symbols
overflow-checks = true # panic on integer overflow

[profile.release]
opt-level = 3          # maximum optimisation
debug = false          # strip debug symbols
lto = true             # link-time optimisation (even smaller/faster binary)
codegen-units = 1      # slower compile, better optimisation
panic = "abort"        # smaller binary, slightly faster

[profile.dev.package."*"]
# Optimise all dependencies even in dev mode (useful for image/crypto crates)
opt-level = 3
Warning
Always benchmark with `cargo build --release`. Debug builds can be 10–100× slower than release builds for computation-heavy code — never draw performance conclusions from a debug build.
A realistic Cargo.toml example

Here is a complete Cargo.toml for a real async web-scraping CLI tool using tokio and serde:

TOML
[package]
name = "web-scraper"
version = "1.0.0"
edition = "2021"
authors = ["Alice <alice@example.com>"]
description = "Async CLI web scraper"
license = "MIT OR Apache-2.0"

[dependencies]
# Async runtime
tokio = { version = "1", features = ["full"] }

# HTTP client (async, with JSON support)
reqwest = { version = "0.11", features = ["json", "gzip"] }

# Serialise/deserialise JSON and TOML
serde = { version = "1", features = ["derive"] }
serde_json = "1"

# Error handling
anyhow = "1"
thiserror = "1"

# HTML parsing
scraper = "0.17"

# CLI argument parsing
clap = { version = "4", features = ["derive"] }

# Logging
tracing = "0.1"
tracing-subscriber = "0.3"

[dev-dependencies]
tokio-test = "0.4"
wiremock = "0.5"

[profile.release]
opt-level = 3
lto = true
cargo doc

Cargo can generate beautiful HTML documentation for your project and all its dependencies directly from source code doc comments.

Bash
# Build documentation
cargo doc

# Build and immediately open in your browser
cargo doc --open

# Include documentation for private items too
cargo doc --document-private-items

# Build docs for a specific package in a workspace
cargo doc -p my_crate --open
Success
Cargo's toolchain integration means you get a consistent workflow across every Rust project. Learn these commands once and they work everywhere.
Tip
Run `cargo clippy -- -D warnings` in CI to treat every Clippy warning as a build error. This keeps code quality high automatically.