The Cargo Guide

Cargo Install and Executable Distribution

Why cargo install Exists

cargo install is Cargo's command for building and installing executable crates into a user-level installation root. It is not the same thing as adding a dependency to a project. A useful mental model is:

  • cargo add or [dependencies] bring code into a package build
  • cargo install installs command-line tools for direct use on a machine

This means cargo install is more like application or tool distribution than library consumption.

What cargo install Can Install

cargo install installs packages that provide executable targets. In practice, that usually means crates with binary targets. Cargo can also install example targets when asked explicitly.

A useful mental model is:

  • libraries are usually consumed as dependencies
  • binaries are usually consumed through cargo install

A simple binary crate might look like this:

[package]
name = "hello_tool"
version = "0.1.0"
edition = "2024"
fn main() {
    println!("hello from an installed tool");
}

Installing from crates.io

The default source for cargo install is crates.io.

Examples:

cargo install ripgrep
cargo install cargo-edit

A useful mental model is:

  • if you do not specify another source, Cargo assumes the package comes from the default registry
  • this is the normal path for installing public Rust command-line tools

Installing from a Specific Version

You can ask Cargo to install a specific version or version requirement from crates.io.

Examples:

cargo install ripgrep --version 14.1.0
cargo install ripgrep --version "~14.0"

A key detail is that cargo install --version 14.1.0 is treated as an exact version, not as an implicit caret requirement the way dependency versions in Cargo.toml usually are.

This makes version pinning for installed tools more explicit and predictable.

Installing from Git

Cargo can install a binary crate directly from a git repository.

Examples:

cargo install --git https://github.com/BurntSushi/ripgrep
cargo install --git https://github.com/example/tool --branch main tool
cargo install --git https://github.com/example/tool --tag v1.2.0 tool
cargo install --git https://github.com/example/tool --rev abc1234 tool

This is useful when:

  • the tool is not published yet
  • you need a specific branch, tag, or revision
  • you are testing a version newer than the latest registry release

Installing from a Local Path

Cargo can also install from a local package path.

Example:

cargo install --path .
cargo install --path ../my_tool

This is useful for locally developed tools, internal utilities, or testing install behavior before publishing.

A useful mental model is:

  • --path turns cargo install into a local executable deployment step
  • it is often a good way to test the real installed-tool experience of a crate

Installing from an Alternate Registry

Cargo can install from a named alternate registry.

Example:

cargo install --registry company internal-tool

This is useful in organizations that distribute internal command-line tools through a private registry instead of crates.io.

A typical config entry for that registry might look like this:

[registries.company]
index = "sparse+https://packages.example.com/index/"

Install Roots

Installed binaries are placed under an installation root, specifically in that root's bin directory.

Cargo determines the install root in precedence order from:

  • --root
  • CARGO_INSTALL_ROOT
  • install.root in Cargo config
  • CARGO_HOME
  • $HOME/.cargo

Examples:

cargo install --root /opt/mytools ripgrep
CARGO_INSTALL_ROOT=/tmp/cargo-root cargo install ripgrep

A Small Root Example

Suppose you want to keep a tool isolated from your main user install location.

Example:

cargo install --root ./local-tools ripgrep

This produces a local installation layout conceptually like:

local-tools/
└── bin/
    └── rg

This is useful for testing, hermetic tool bundles, or project-scoped utility sets.

How cargo install Chooses What to Install

If a package has multiple executable targets, Cargo installs binaries by default. You can narrow or broaden that selection with explicit flags.

Examples:

cargo install mytool --bin admin
cargo install mytool --bins
cargo install mytool --example demo
cargo install mytool --examples

A useful mental model is:

  • package selection chooses the crate source
  • target selection chooses which executables from that crate are installed

A Multi-Binary Example

Suppose a crate has this layout:

mytool/
ā”œā”€ā”€ Cargo.toml
└── src/
    ā”œā”€ā”€ main.rs
    └── bin/
        └── admin.rs

Then you can install only the admin binary like this:

cargo install --path . --bin admin

Or install all binaries like this:

cargo install --path . --bins

Reinstall and Update Behavior

If a package is already installed, Cargo decides whether to reinstall it based on whether the installed package still appears up to date. Cargo reinstalls when relevant installation inputs have changed, such as:

  • package version or source
  • installed binary names
  • chosen features
  • selected profile
  • selected target

This means cargo install is not only a one-time action. It also acts as an update or replacement mechanism when the requested installation meaningfully differs from what is already present.

Forcing Reinstallation

You can force Cargo to reinstall even if it would otherwise consider the installation current.

Example:

cargo install --force ripgrep
cargo install --path . --force

This is useful when:

  • you want to rebuild with a newer compiler or changed system environment
  • you suspect stale installation state
  • the binary name conflicts with another package and you intentionally want to overwrite it

Listing Installed Packages

Cargo can list installed packages and their versions.

Example:

cargo install --list

This is a useful inspection command when you want to understand what Cargo believes is installed in the current install root.

Version Pinning for Operational Stability

For operational tooling, version pinning can be important.

Examples:

cargo install --version 1.2.3 mytool
cargo install --git https://github.com/example/tool --rev abc1234 tool

A useful mental model is:

  • exact versions or revisions improve repeatability
  • looser requirements trade some repeatability for easier updates

Lockfile Behavior and --locked

By default, cargo install ignores the Cargo.lock file packaged with the crate and recomputes dependency resolution. That means an installed tool may use newer compatible dependency versions than the author had when the package was published.

If you want Cargo to use the packaged lockfile when available, use:

cargo install --locked ripgrep

A useful mental model is:

  • default install behavior favors fresh compatible resolution
  • --locked favors reproducibility against the packaged lockfile

Why --locked Can Matter for Tool Distribution

--locked is especially useful when:

  • you want the exact dependency graph packaged by the author
  • a newer dependency release causes build issues on your system
  • you want more reproducible installation across machines

The tradeoff is that you may miss dependency fixes that were published after the tool release.

Build Profiles for Installed Tools

By default, cargo install builds with the release profile, which fits its role as an executable distribution command.

You can choose a different profile when needed.

Examples:

cargo install --debug mytool
cargo install --profile profiling mytool

A useful mental model is:

  • installed tools are usually meant to be production-style executables
  • profile selection lets you tune that behavior when you need debugging or special build modes

Target Selection for Installed Tools

Cargo install also supports target selection.

Example:

cargo install --target aarch64-unknown-linux-gnu mytool

This is useful in cross-compilation or deployment workflows, though the target toolchain and link environment still need to exist for the selected target.

Target Directories and Build Caching

For crates.io and git installs, Cargo normally builds in a temporary target directory. To reuse build artifacts and improve repeated install performance, you can set CARGO_TARGET_DIR to a relative path.

Example:

CARGO_TARGET_DIR=target-install-cache cargo install ripgrep

This can be especially useful in CI or repeated tool bootstrap workflows.

Configuration Discovery Behavior

cargo install behaves differently from normal project-oriented Cargo commands when discovering config. It operates at system or user level rather than project level, so local configuration discovery is ignored by default. Instead, discovery begins at $CARGO_HOME/config.toml.

An important exception is when using --path: local config discovery begins at the selected path's .cargo/config.toml.

This is a subtle but important operational caveat because install behavior may differ from ordinary cargo build expectations.

Why Config Discovery Is an Operational Caveat

A user may assume that a nearby repository .cargo/config.toml automatically affects cargo install, but that is not generally true for registry and git installs. This can be surprising in environments with custom linkers, runners, registry settings, or aliases.

A practical mental model is:

  • cargo install is user/system oriented
  • normal project builds are project oriented
  • --path is the bridge case where local project config matters again

Application Packaging vs Library Publishing

A healthy Cargo ecosystem distinction is:

  • library crates are usually published for use as dependencies
  • binary crates are often published for use with cargo install

This does not mean one crate cannot contain both a library and binaries, but it does mean the user experience is different.

A library-oriented crate is consumed in Cargo.toml:

[dependencies]
my_lib = "0.1"

A tool-oriented crate is consumed operationally:

cargo install my_tool

Why Binary Distribution Needs Different Thinking

A crate intended for cargo install should usually be curated as an executable product, not just as a code package. That often means paying special attention to:

  • installation reliability
  • CLI UX
  • README usage examples
  • version pinning guidance when appropriate
  • reproducible installs through --locked when relevant
  • avoiding unnecessary heavy build dependencies if the tool is meant for broad use

A Small Tool-Oriented Package Example

Suppose a crate is meant to be installed as a command-line tool.

Manifest:

[package]
name = "task_runner"
version = "0.3.0"
edition = "2024"
description = "A small task-running CLI"
license = "MIT"
readme = "README.md"
repository = "https://github.com/example/task_runner"

Source:

fn main() {
    println!("task_runner: ready");
}

Typical user installation:

cargo install task_runner

Typical development install test:

cargo install --path .

Operational Caveats of cargo install

There are several practical caveats to keep in mind.

First, cargo install is a source-build workflow, not a binary-package-manager workflow. Users need a working Rust toolchain and whatever native build prerequisites the crate requires.

Second, default dependency resolution may differ over time unless --locked is used.

Third, config discovery differs from normal project commands.

Fourth, large or native-heavy tools may be slower or more fragile to install than users expect.

These are not flaws in Cargo so much as consequences of what cargo install is designed to do.

When cargo install Is a Good Fit

cargo install is a strong fit when:

  • the audience already uses Rust tooling
  • the executable is open-source and easily built from source
  • reproducibility is acceptable through version pins or --locked
  • native system requirements are reasonable or well documented

It is often less ideal when users are not expected to have Rust installed or when the build prerequisites are unusually heavy.

Using --path to Test the Real Installed Experience

A very useful local workflow is to test the installed-tool experience directly from your working tree.

Example:

cargo install --path . --force

This helps answer questions like:

  • does the package install cleanly as an executable?
  • are the intended binaries selected correctly?
  • does the README's installation guidance match reality?

This is often more revealing than only using cargo run during development.

A Practical Install Workflow

A simple development and release workflow for a binary crate might look like this:

cargo test
cargo install --path . --force
cargo publish --dry-run
cargo publish

And for users who need stable reproduction:

cargo install --locked task_runner

Common Beginner Mistakes

Mistake 1: confusing cargo install with adding a project dependency.

Mistake 2: assuming installed tools always update automatically rather than needing reinstall logic.

Mistake 3: forgetting that --version 1.2.3 for install is exact rather than an implicit caret range.

Mistake 4: assuming local project config always affects install behavior.

Mistake 5: publishing a binary crate without testing cargo install --path . first.

Mistake 6: forgetting that source-based tool installation may require native prerequisites and a working Rust toolchain.

Hands-On Exercise

Create a small binary crate and exercise several install modes.

Start here:

cargo new install_lab
cd install_lab

Use this program:

fn main() {
    println!("hello install lab");
}

Then try these commands:

cargo install --path .
cargo install --path . --force
cargo install --root ./local-root --path .
cargo install --list

If you want to explore root behavior, inspect the result:

find ./local-root -maxdepth 2 -type f | sort

Then compare the role of cargo install --path . with the role of publishing and installing by name from a registry. This makes the difference between local executable deployment and public executable distribution concrete.

Mental Model Summary

A strong mental model for cargo install is:

  • it installs executable crates for direct machine use, not library dependencies for project builds
  • it supports crates.io, git, path, and alternate-registry sources
  • install roots, selected binaries, chosen features, target, and profile all affect installation identity
  • version pinning for install is more exact and operational than dependency versioning in manifests
  • --locked improves reproducibility by using the packaged lockfile when available
  • config discovery is more user/system oriented than ordinary project builds, except for --path
  • cargo install is a source-build executable distribution mechanism, with all the operational tradeoffs that implies

Once this model is stable, Cargo install becomes much easier to reason about as a tool distribution workflow rather than as a variant of dependency management.