| Contents |

23 Working with Cargo

Cargo is Rust’s official build system and package manager, integral to the Rust development experience. It streamlines essential tasks such as creating new projects, managing dependencies, compiling code, running tests, and publishing packages to the central registry, Crates.io. While previous chapters introduced basic Cargo usage for building and running code (Chapter 4) and managing dependencies (Chapter 17), this chapter delves deeper.

We will explore Cargo’s command-line interface (CLI), the standard project structure it encourages (often managed with version control systems like Git), dependency version management, and the distinction between building libraries and binary applications. Further topics include publishing your own packages, customizing build configurations (profiles), organizing larger projects with workspaces, and generating project documentation.

Cargo is a powerful tool with many features; this chapter focuses on the capabilities most relevant for developers, particularly those coming from C or C++ backgrounds where build systems (like Make or CMake) and package managers (like Conan or vcpkg) are often separate entities. For exhaustive details, refer to the official Cargo Book.

Note that Cargo’s testing and benchmarking features (cargo test, cargo bench) are covered in the next chapter.

A brief note on terminology: In Rust, the terms crate and package are often used interchangeably in common conversation, but they have distinct meanings in the context of Cargo. A package is a unit that Cargo builds, publishes, and downloads. It contains a Cargo.toml file (the manifest) and one or more crates. A package must contain at least one library crate or one binary crate. It can contain a library crate and multiple binary crates. Crates are the fundamental units of compilation (a library crate or a binary crate). When people refer to downloading a dependency from Crates.io, they are technically downloading a package, although it is common to hear this referred to as downloading a “crate”. Since most library packages contain only a single library crate, using “crate” when “package” is meant is often not significantly misleading. The term project is also often used synonymously with package, particularly when referring to the directory structure created by cargo new. This chapter will use the terms package and crate more precisely where the distinction is relevant, but acknowledge the common community usage.


23.1 Overview

Cargo automates and standardizes many aspects of Rust development. Its core functions include:

Comparison with C/C++ Build Systems and Package Managers

Coming from C or C++, you might be accustomed to using separate tools:

Cargo unifies these roles. It manages both the build process (invoking the Rust compiler rustc with appropriate flags for your crates) and dependency resolution in a single, integrated tool with a consistent interface across all Rust packages. This significantly simplifies project setup and maintenance compared to the fragmented C/C++ ecosystem.


23.2 The Cargo Command-Line Interface (CLI)

Cargo is primarily used via the command line. You can verify your installation and see available commands:

cargo --version
cargo --help

Below are some of the most frequently used Cargo commands.

23.2.1 cargo new and cargo init

These commands initialize a new Rust project (package).

Use the --lib flag to create a library package instead of the default binary (application) package:

# Create a new binary application package named 'hello_world'
cargo new hello_world

# Create a new library package named 'my_utils'
cargo new my_utils --lib

# Initialize the current directory as a Cargo package (defaults to binary)
cargo init

# Initialize './existing_lib_dir' as a library package
cargo init --lib ./existing_lib_dir

23.2.2 cargo build and cargo run

These commands compile and execute your code.

# Build the package in debug mode
cargo build

# Build and run the package's default binary in debug mode
cargo run

Release Mode

For production builds or performance testing, use release mode. This enables more aggressive compiler optimizations, resulting in slower compilation but faster runtime performance and smaller binaries. Debug information is typically omitted.

# Build with release optimizations
cargo build --release

# Build and run in release mode
cargo run --release

Release artifacts are placed in a separate target/release/ directory. Incremental compilation behaves differently in release mode, as discussed in Section 23.5.1.

23.2.3 cargo check

This command quickly checks your code for compilation errors without generating any executable code. It performs parsing, type checking, and borrow checking on the crates within your package.

cargo check

cargo check is significantly faster than cargo build, especially for larger projects, because it skips the code generation (LLVM) phase. It’s useful for getting rapid feedback during development. It also benefits from incremental checking.

23.2.4 cargo clean

Removes the target/ directory, deleting all compiled artifacts (executables, libraries, intermediate files) for the current package.

cargo clean

This is useful when you suspect build issues might be related to stale artifacts, need to force a full rebuild of the package’s crates, or want to free up disk space.

23.2.5 cargo add, cargo remove, cargo upgrade

These commands manage dependencies listed in your Cargo.toml. They operate on dependency packages.

# Add the 'serde' package as a dependency
cargo add serde

# Add 'rand' as a development-only dependency package (for tests, examples)
cargo add rand --dev

# Add a specific version of the 'serde' package with a feature enabled
cargo add serde --version "1.0.150" --features "derive"

# Remove the 'rand' package
cargo remove rand

These commands modify Cargo.toml and automatically update Cargo.lock (see Section 23.4.3). Before Rust 1.62, cargo add and remove were part of the external cargo-edit tool. They are now built-in.

23.2.6 cargo fmt

Formats your package’s Rust code according to the community-standard style guidelines using the rustfmt tool.

cargo fmt

Running cargo fmt regularly helps maintain a consistent code style across the project’s crates, reducing cognitive load and preventing style-related noise in code reviews and version control history.

23.2.7 cargo clippy

Runs Clippy, Rust’s official collection of lints. Clippy provides suggestions to improve code correctness, performance, style, and idiomatic usage within your package’s crates.

cargo clippy

Clippy often catches potential bugs or suggests better ways to express logic. It’s highly recommended to run clippy as part of your development workflow and CI process.

23.2.8 cargo fix

Automatically applies suggestions made by the Rust compiler (rustc) or Clippy to fix warnings or simple errors in your package’s code.

# Apply compiler suggestions
cargo fix

# Apply suggestions, even with uncommitted changes (use with caution)
# (This assumes changes are staged or you accept working with a dirty tree)
cargo fix --allow-dirty

Always review the changes made by cargo fix before committing them to your version control system.

23.2.9 cargo doc

Generates HTML documentation for your package and its dependencies based on documentation comments in the source code of its crates.

# Generate documentation (output in target/doc/)
cargo doc

# Generate documentation and open the main page in a browser
cargo doc --open

# Generate docs only for your package's own crates (not dependencies)
cargo doc --no-deps

Documentation generation is covered further in Section 23.8.

23.2.10 Extending Cargo: cargo install and External Tools

Cargo can be extended with custom subcommands. You can install additional tools distributed as binary packages using cargo install.

Examples of useful tools installable via cargo install:

# Install the cargo-edit package (and its binary crate)
cargo install cargo-edit

# Now you can use 'cargo upgrade'
cargo upgrade

# Install Miri and run
rustup component add miri
cargo miri run

23.3 Standard Project Directory Structure

cargo new and cargo init create a standard directory layout for a package:

my_package/
├── .git/            # Git repository data (if initialized by `cargo new`)
├── .gitignore       # Git ignore file (typically includes /target/)
├── Cargo.toml       # Package manifest file
├── Cargo.lock       # Locked dependency versions (generated after first build/add)
├── src/             # Source code directory for crates
│   └── main.rs      # Crate root for a binary crate
│   # Or:
│   └── lib.rs       # Crate root for a library crate
└── target/          # Build artifacts (compiled code, cache) - not version controlled

23.4 The Manifest: Cargo.toml

The Cargo.toml file is the heart of a Rust package. It uses the TOML (Tom’s Obvious, Minimal Language) format to define metadata and dependencies.

23.4.1 Common Sections

A typical Cargo.toml includes several sections:

[package]
name = "my_package" # Name of the package (often matches the main crate name)
version = "0.1.0"
edition = "2024" # Specifies the Rust edition
authors = ["Your Name <you@example.com>"]
description = "A short description of what my_package does."
license = "MIT OR Apache-2.0" # SPDX license expression
repository = "https://github.com/your_username/my_package" #Optional: URL to source
readme = "README.md" # Optional: Path to README file
keywords = ["cli", "utility"] # Optional: Keywords for Crates.io search

[dependencies]
# Lists packages needed to compile and run the package's code
serde = { version = "1.0", features = ["derive"] } # Example with version and features
rand = "0.8"
log = "0.4"

[dev-dependencies]
# Lists packages needed only for tests, examples, and benchmarks
assert_cmd = "2.0"
criterion = "0.4"

[build-dependencies]
# Lists packages needed by build scripts (build.rs)
# Example: cc = "1.0"

[features]
# Defines optional features for conditional compilation
default = ["std_feature"] # Default features enabled if none specified
std_feature = []
serde_support = ["dep:serde"] # Feature enabling an optional dependency package

[profile.release]
# Customizes the 'release' build profile (e.g., for optimizations)
opt-level = 3        # Optimization level (0-3, 's', 'z')
lto = true           # Enable Link-Time Optimization
codegen-units = 1    # Fewer codegen units for potentially better optimization

# See also: [profile.dev], [profile.test], [profile.bench]

[[bin]]
name = "my_cli" # Define an additional binary crate within this package
path = "src/bin/cli.rs"

23.4.2 Specifying Dependencies

Dependencies are listed under the [dependencies] (or [dev-dependencies], [build-dependencies]) section. Each dependency specifies the package name and a version requirement.

Cargo and the broader Rust ecosystem adhere to Semantic Versioning (SemVer), as defined at semver.org. SemVer versions are typically in the format MAJOR.MINOR.PATCH:

A key point in the SemVer specification (specifically rule #4) is that “Major version zero (0.y.z) is for initial development. Anything MAY change at any time. The public API SHOULD NOT be considered stable.” This means, strictly by SemVer, a 0.1.0 version can have breaking changes introduced in 0.2.0 or even 0.1.1.

However, the Rust community has adopted a common convention for 0.y.z versions that offers more practical stability:

This convention allows Cargo to provide sensible default dependency update behavior. When you specify a version for a dependency package:

[dependencies]
regex = "1.5"    # For versions >= 1.0.0
serde = "0.8.2"  # For versions < 1.0.0

Cargo interprets these version strings using a caret requirement (^) by default:

This default behavior allows you to receive compatible updates automatically while guarding against breaking changes. Other common version specifiers offer more control:

You can also specify dependencies from other sources:

[dependencies]
# From a Git repository containing the package
some_lib = { git = "https://github.com/user/some_lib.git", branch = "main" }

# From a local path (useful during development or in workspaces)
local_util = { path = "../local_util" }

# With optional features enabled
# Here, "1.0" implies "^1.0.0"
serde_json = { version = "1.0", features = ["raw_value"] }

# Marked as optional (only included if a feature in your crate enables it)
# In [dependencies]:
#    mio = { version = "0.8", optional = true } # "0.8" implies "^0.8.0"
# In [features]:
#    network = ["dep:mio"]

Understanding these conventions is crucial for managing dependency packages effectively and ensuring your project remains buildable and stable as your dependencies evolve.

23.4.3 The Cargo.lock File

When you build your package for the first time, or after modifying dependencies in Cargo.toml, Cargo resolves all dependency packages (including transitive ones) and records the exact versions used in the Cargo.lock file.

23.4.4 Updating Dependencies


23.5 Building and Running Projects

As discussed in Section 23.2.2, cargo build compiles your package’s crates, and cargo run compiles and then executes the default binary crate. Both default to debug mode unless --release is specified.

23.5.1 Build Cache and Incremental Compilation

Cargo employs several caching mechanisms to speed up builds:

These mechanisms significantly reduce build times during typical development workflows.

23.5.2 Cross-Compilation

Cargo can compile the crates within your package for different target architectures (e.g., ARM for Raspberry Pi from an x86 machine) using the --target flag. You first need to add the target via rustup:

# Add the ARMv7 Linux target
rustup target add armv7-unknown-linux-gnueabihf

# Build the package for that target
cargo build --target armv7-unknown-linux-gnueabihf

Cross-compilation might require setting up appropriate linkers for the target system.


23.6 Build Profiles

Build profiles allow you to configure compiler settings for different scenarios when building your package’s crates. Cargo defines four profiles by default: dev, release, test, and bench. The dev and release profiles are the most commonly used.

You can customize these profiles in Cargo.toml under [profile.*] sections:

[profile.dev]
opt-level = 1    # Enable basic optimizations even in debug builds
# debug = 2      # Use '2' for full debug info, '1' for line tables only, '0' for none
# incremental = true # Default for dev

[profile.release]
lto = "fat"      # Enable "fat" LTO for potentially better performance/size
codegen-units = 1 # Reduce parallelism, potentially better optimization (slower build)
panic = 'abort'   # Abort on panic instead of unwinding (can reduce binary size)
# strip = true    # Strip symbols from the binary (requires Rust 1.59+)
# incremental = false # Default for release

Key profile settings include:

Profile settings in a dependency package’s Cargo.toml are ignored; only the settings in the top-level package’s Cargo.toml (the one being built directly) are used.


23.7 Testing and Benchmarking (Overview)

Cargo provides first-class support for running tests and benchmarks defined within your package’s crates, which are covered in detail in the next chapter.


23.8 Generating Documentation

Rust places a strong emphasis on documentation, and Cargo makes generating and viewing it easy for your package’s crates.

23.8.1 Documentation Comments

Rust uses specific comment styles for documentation, written in Markdown:

//! This package contains a crate that provides utility functions for string
//! manipulation. Use `add_prefix` from the `my_string_utils` crate to prepend text.

/// Adds a prefix to the given string.
///
/// # Examples
///
/// ```
/// let result = my_string_utils::add_prefix("world", "hello ");
/// assert_eq!(result, "hello world");
/// ```
///
/// # Panics
///
/// This function does not panic.
///
/// # Errors
///
/// This function does not return errors.
pub fn add_prefix(s: &str, prefix: &str) -> String {
    format!("{}{}", prefix, s)
}

Good documentation explains the purpose, parameters, return values, potential errors or panics, usage examples (which double as doc tests), and safety considerations (especially for unsafe code).

23.8.2 cargo doc

The cargo doc command invokes the rustdoc tool to extract these comments and generate HTML documentation for your package’s public crates.

# Generate documentation (output in target/doc/)
cargo doc

# Generate documentation and open the main page in a browser
cargo doc --open

# Generate docs only for your package's own crates (not dependencies)
cargo doc --no-deps

The generated documentation, located in target/doc, provides a navigable interface for your package’s public API (the public items within its library crate(s)) and the APIs of its dependency packages.

23.8.3 Re-exporting for API Design

As mentioned in Chapter 17, you can use pub use statements within a library crate to re-export items from modules or dependency crates, creating a cleaner and more stable public API surface for your library package. This also affects how the API appears in the generated documentation.


23.9 Publishing Packages to Crates.io

Crates.io is the official Rust package registry. Publishing your library package allows others to easily use its contained crate(s) as dependencies. Most Rust projects (packages) are managed using Git for version control, and it’s common practice to host them on platforms like GitHub or GitLab. Before publishing, ensure your package is in a clean state in your version control system.

23.9.1 Prerequisites

  1. Account: Create an account on Crates.io, usually via GitHub authentication.

  2. API Token: Generate an API token in your account settings on Crates.io.

  3. Login via Cargo: Authenticate your local Cargo installation with the token:

    cargo login <your_api_token>
    # Paste the token when prompted or provide it directly (less secure)

    This stores the token locally (typically in ~/.cargo/credentials.toml).

23.9.2 Preparing Cargo.toml

Before publishing, ensure your Cargo.toml contains the required metadata in the [package] section for the package you want to publish:

23.9.3 The Publishing Process

  1. Version Control: Ensure all changes intended for the release are committed to your Git repository. The source code published to Crates.io should ideally match a tagged commit in your repository for easy reference.

  2. Package (Optional but Recommended): Simulate the packaging process to check for errors and see exactly which files will be included in the archive that gets uploaded:

    cargo package

    Cargo uses .gitignore (and potentially a .cargoignore file if you need finer control beyond what .gitignore offers for packaging) to exclude unnecessary files. Review the generated .crate file (a compressed archive) in target/package/ if needed.

  3. Publish: Upload the package to Crates.io:

    cargo publish

Once published, the specific version of the package is permanent (though it can be “yanked”). Other users can now add your package as a dependency:

[dependencies]
your_package_name = "0.1.0"

23.9.4 Updating and Yanking

23.9.5 Deleting Packages

Published package versions cannot be deleted from Crates.io to ensure builds that depend on them remain reproducible. Yanking is the standard mechanism for indicating problematic versions. In truly exceptional circumstances, you might contact the Crates.io team.


23.10 Binary vs. Library Crates

Cargo distinguishes between packages that produce executable binary crates and those that produce library crates:

A single package can contain both a library crate and one or more binary crates:

Cargo will build the library crate and all specified binary crates within the package. This pattern is common for packages that provide both a reusable library API and a command-line tool interface (as separate crates within the same package).


23.11 Cargo Workspaces

Workspaces allow you to manage multiple related packages within a single top-level structure. All packages in a workspace share a single target/ directory (for build artifacts from all their contained crates) and a single Cargo.lock file (ensuring consistent dependency versions across all member packages).

23.11.1 Use Cases

Workspaces are useful for:

  * Large Projects: Breaking down a complex application or library into smaller, more manageable internal packages, each containing one or more crates.   * Related Packages: Developing several packages (e.g., a core library, a CLI frontend, a web server) that depend on each other.   * Monorepos: Managing multiple distinct but potentially related projects/packages in one repository.

23.11.2 Setting Up a Workspace

1.  Create a top-level directory for the workspace. 2.  Inside it, create a Cargo.toml file that defines the workspace members (packages). This file typically doesn’t define a [package] itself (it’s a “virtual manifest”), only the [workspace] section. 3.  You can either place the directories of the individual member packages (each containing its own Cargo.toml) directly inside the workspace directory, or you can specify paths to these package directories within the members array in the workspace’s Cargo.toml if they reside elsewhere relative to the workspace root.

my_workspace/
├── Cargo.toml         # Workspace root manifest
├── member_lib/        # A library package
│   ├── Cargo.toml
│   └── src/lib.rs     # The library crate root
└── member_bin/        # A binary package using the library package
    ├── Cargo.toml
    └── src/main.rs    # The binary crate root
# Shared target and lock file will appear here after build:
# ├── Cargo.lock
# └── target/

my_workspace/Cargo.toml:

[workspace]
members = [
    "member_lib",
    "member_bin",
    # You can also use globs: "packages/*"
    # Or specify paths to members outside the immediate workspace directory:
    # "../another_project_package",
]

# Optional: Define settings shared across the workspace
[workspace.dependencies]
# Define common dependency packages once here
# Example:
# serde = { version = "1.0", features = ["derive"] }

# Member packages can then inherit this:
# In member_bin/Cargo.toml under [dependencies]:
# serde = { workspace = true } # Inherits version and other details from workspace

# Optional: Configure dependency resolution strategy
resolver = "3" # Use the version 3 feature resolver (default for Rust 2024 edition)

The resolver field specifies which dependency resolution algorithm Cargo should use. Version “2” was the default since Rust 1.51, and resolver = "3" is the default for the Rust 2024 edition.

my_workspace/member_bin/Cargo.toml:

[package]
name = "member_bin"
version = "0.1.0"
edition = "2021"

[dependencies]
# Reference the library package within the workspace via path
member_lib = { path = "../member_lib" }
# Or if 'serde' was defined in [workspace.dependencies]:
# serde = { workspace = true } # Inherits version and other details from workspace

When you create a new package inside a workspace using cargo new <package-name>, Cargo automatically adds it to the members key in the workspace’s Cargo.toml.

23.11.3 Working with Workspaces

  * Cargo commands run from the workspace root directory (e.g., cargo build, cargo test, cargo check) operate on all member packages by default, building their respective crates.   * Use the -p <package_name> or --package <package_name> flag to target a specific member package:     ```bash     # Build only the member_bin package     cargo build -p member_bin

    # Run the default binary crate from the member_bin package     cargo run -p member_bin

    # Test only the member_lib package     cargo test -p member_lib     ```

23.11.4 Publishing Packages from a Workspace

Workspaces themselves are not published to Crates.io as a single unit. Instead, individual member packages within a workspace can be published if they are configured as such (i.e., they have the necessary [package] metadata and are not marked publish = false in their respective Cargo.toml files).

  * To publish a specific package from a workspace, navigate to that package’s directory and run cargo publish, or run cargo publish -p <package_name> from the workspace root.   * If you run cargo publish from the workspace root without the -p flag, Cargo will attempt to publish all publishable member packages in the dependency order.

Workspaces are primarily an organizational and build management tool for local development and repository structure. They help ensure that related packages and their contained crates are built and tested together with consistent dependencies.

23.11.5 Benefits

  * Shared Build Cache: Dependency packages are compiled only once for the entire workspace, saving time and disk space.   * Consistent Dependency Versions: A single Cargo.lock at the workspace root ensures all member packages use the exact same resolved versions of external dependencies.   * Easier Inter-Package Development: Changes in one member package are immediately available to other member packages in the workspace that depend on it (via their contained crates), without needing to publish intermediate versions or use path overrides extensively if not for a workspace setup. This allows you to work on interdependent crates within the workspace as if they were a single project.   * Atomic Operations: Running tests, checks, or builds across the entire collection of related packages is straightforward.


Some more Details about Dependency Management and Resolution

Internal Workspace Dependencies

When a member package in a workspace depends on another member package (e.g., member_bin depending on member_lib), you must specify this dependency using a path as shown: member_lib = { path = "../member_lib" }. This tells Cargo to look for member_lib at the specified relative path within the workspace. Using just the package name alone (e.g., member_lib = "0.1.0") would instruct Cargo to look for member_lib on Crates.io, which is not the intention for an internal workspace dependency.

External Dependencies and [workspace.dependencies]

When multiple packages in your workspace depend on the same external package (e.g., serde), you have two primary options for declaring these dependencies:

  1. Individual Declarations: Each member package can declare the dependency in its own Cargo.toml file under [dependencies]:

    # member_bin/Cargo.toml
    [dependencies]
    serde = "1.0"
    # member_lib/Cargo.toml
    [dependencies]
    serde = "1.0"

    Cargo’s dependency resolution, managed by the single Cargo.lock file at the workspace root, ensures that only one compatible version of serde is used across the entire workspace. For instance, if member_bin requires serde = "1.0" and member_lib requires serde = "1.0.10", Cargo will attempt to find a single version that satisfies both, such as 1.0.10. If incompatible versions are requested (e.g., 1.0 and 2.0), Cargo will report an error. While Cargo can sometimes resolve multiple major versions of the same crate if their feature sets do not conflict, it is generally recommended to standardize on a single major version across your workspace to avoid increased build times and binary size.

  2. Workspace Inheritance ([workspace.dependencies]): This is a more convenient and robust approach for managing common external dependencies. By defining serde (or any other external dependency) once in [workspace.dependencies] in the workspace’s Cargo.toml:

    # my_workspace/Cargo.toml
    [workspace.dependencies]
    serde = { version = "1.0", features = ["derive"] }

    Member packages can then inherit this definition by using serde = { workspace = true } in their own Cargo.toml:

    # member_bin/Cargo.toml
    [dependencies]
    serde = { workspace = true }
    # member_lib/Cargo.toml
    [dependencies]
    serde = { workspace = true }

    This mechanism ensures that all member packages using serde = { workspace = true } will use the exact same version and features as defined in the workspace root. This is particularly beneficial for consistency and preventing subtle version mismatches across your related packages.

    When you publish a package (e.g., member_lib) that uses serde = { workspace = true }, Cargo automatically resolves this during the packaging process. The Cargo.toml file within the published .crate archive will not contain serde = { workspace = true }. Instead, Cargo replaces it with the actual version and details that were inherited from the workspace’s Cargo.toml. For example, if my_workspace/Cargo.toml specified serde = { version = "1.0", features = ["derive"] }, the published member_lib/Cargo.toml would contain serde = { version = "1.0", features = ["derive"] }. This ensures that the published package is self-contained and can be used as a dependency by other projects, even outside your workspace, without needing the workspace context.


23.12 Installing Binary Packages with cargo install

Besides building your own projects, you can install Rust applications published on Crates.io directly using cargo install. This command downloads the specified package, builds its default binary crate (usually in release mode), and places the resulting executable in your Cargo bin directory.

cargo install ripgrep # Installs the 'ripgrep' fast search tool package
cargo install fd-find # Installs the 'fd' find alternative package

Cargo downloads the package’s source code, compiles its binary crate in release mode, and places the resulting binary in ~/.cargo/bin/. Ensure this directory is included in your system’s PATH environment variable to run the installed commands directly (e.g., rg, fd).

Important note regarding Cargo.lock and cargo install: When you run cargo install, it builds the specified binary package from source. Unlike cargo build or cargo run (which, for a local project, will automatically use an existing Cargo.lock file to ensure reproducible builds), cargo install by default ignores the Cargo.lock file that might be present in the downloaded package’s source. Instead, it resolves dependencies based on Cargo.toml and creates a new Cargo.lock for the installation process.

This behavior is generally safe for libraries (where the exact dependency versions are less critical for the library itself, as the downstream application’s Cargo.lock will govern). However, for installing application binaries, it means you might get a different set of dependency versions than what the application developer used or tested with, potentially leading to different behavior or even breakage if a dependency introduced an incompatible change.

To ensure a reproducible installation that uses the exact dependency versions specified by the application’s developer (i.e., those recorded in the Cargo.lock file shipped with the package), you should use the --locked flag:

# Install with reproducible dependencies as specified by the package author
cargo install ripgrep --locked

Using --locked is highly recommended for installing application binary packages to ensure you get the same executable artifact that the author intended. If the package’s Cargo.lock file is missing or out of sync with Cargo.toml, cargo install --locked will fail, prompting the package author to fix their distribution.

Use cargo install --list to see installed packages. To update an installed package, run cargo install again with the same package name (or cargo install <package_name> --locked for reproducibility). To uninstall, use cargo uninstall <package_name>.


23.13 Security Considerations

While Crates.io and Cargo provide a convenient way to share and use code, dependency packages introduce potential security risks (supply chain attacks).


23.14 Summary

Cargo is the cornerstone of the Rust development workflow, integrating build automation, dependency management, and various development tools into a single, cohesive system. Key takeaways include:

Mastering Cargo is essential for productive Rust development. Its conventions and capabilities foster consistency, reliability, and collaboration within the Rust ecosystem.



| Contents |