Rust is an amazing language to work with. However, it comes with an oft-misunderstood tool known as Cargo.
Many people, when they first get started with Rust, have trouble grasping what Cargo does and how it works. You might be wondering, for instance, why you need to run your Rust code with cargo run. Why do you need Cargo to run your Rust program? Why are there so many cargo activities in a Rust program?
In this tutorial, we aim to clear up the mystery surrounding Rust Cargo. We’ll cover the following:
Cargo.lock?Cargo.lock vs. Cargo.tomlThe Replay is a weekly newsletter for dev and engineering leaders.
Delivered once a week, it's your curated guide to the most important conversations around frontend dev, emerging AI tools, and the state of modern software.
Cargo is Rust’s build system and package manager. With this tool, you’ll get a repeatable build because it allows Rust packages to declare their dependencies in the manifest, Cargo.toml. When you install Rust through rustup, Cargo is also installed.
For example, when you run the following command on Linux or macOS to install Rust, Cargo is installed as well:
curl https://sh.rustup.rs -sSf | sh
Cargo helps you to compile your Rust program successfully. It downloads dependencies, compiles your packages, and uploads them to the Rust project registry, crates.io.
Cargo allows Rust packages to declare their dependencies. This is done in the Cargo.toml file. All you have to do is declare the dependencies required to run your program in the Cargo.toml file. Next, Cargo extracts all the necessary information about your dependencies and build information into the Cargo.lock file.
The function of the Cargo.lock file is to allow for a repeatable build. Therefore, there should be no cases of different builds, even when you share your project.
You may be wondering how the Cargo.toml and Cargo.lock files are different. Cargo.toml is a manifest, which is a document that contains detailed information about passengers, goods, etc. In Rust, the manifest contains detailed information about a given project, such as the project name, version, dependencies, etc. The manifest tells Cargo which dependency it needs to download to compile your project successfully.
Cargo.lock?When you build your project and share it with others, how does Rust make sure you and your friends don’t have different build with different versions of dependencies? This is where Cargo.lock comes in.
Cargo.lock simply contains build information of your project. When your project builds successfully, all the build information is stored in your Cargo.lock file. This includes the exact version of dependencies for your build. You may wonder why this is important when you have already specified your dependencies version in Cargo.toml.
What happens when the version of your dependencies are updated and it causes your program to function unexpectedly? Since you have the exact version of the dependencies in Cargo.toml, all Cargo will do is download them while trying to compile your program, even if it gets updated. However, if you have a Cargo.lock file, Cargo will use the exact copy of the dependency in the Cargo.lock file. You won’t have to risk one of the SemVer dependencies breaking your program even after an initial successful build with Cargo.lock.
Cargo.lock vs. Cargo.tomlThe first thing to note about Cargo.lock and Cargo.toml is that both contain dependencies for your project. However, Cargo.toml is written by the developer while Cargo.lock is maintained by Cargo.
The reason for using a Cargo.lock file in addition to a Cargo.toml file is to enable repeatable builds across all machines. While Cargo.toml file stores SemVer versions, Cargo.lock stores the exact version of dependency during a successful build.
To understand this better, let’s imagine that there’s no Cargo.lock file and the SemVer restrictions for our dependencies are:
[dependencies] serde = "1.0" serde json = "1.0"
When you build your project, the exact serde version that builds successfully is serde = "1.0.124". If serde is updated and you share your project with a colleague, they may run into some errors because the serde update may not be compatible with your project. Cargo.lock resolves dependency issues by allowing Cargo to compare information in the Cargo.lock file.
With profiles in Rust, it’s easy to change some compiler settings. For instance, you can change the optimization level just by specifying it in the Cargo.toml file:
[profile.dev
opt-level = 1 # Use basic optimizations.
This tells Cargo to use an optimization level of 1 while compiling your project.
Rust has four built-in profiles: dev, test, bench and release. The command running depends on the profile to be chosen. You can change compiler settings in the [profile] table of your Cargo.toml file.
Let’s look at the default profiles as well as the commands and command-line flags associated with each.
DEVWhen you run the command cargo build, the compiler uses the dev profile. This profile is used for normal development and debugging.
[profile.dev]
opt-level = 0
debug = true
debug-assertions = true
overflow-checks = true
lto = false
panic = 'unwind'
incremental = true
codegen-units = 256
rpath = false
RELEASEThe RELEASE profile is used for releases and in production. When you use the cargo install command or -- release flag, this profile will be used.
[profile.release]
opt-level = 3
debug = false
debug-assertions = false
overflow-checks = false
lto = false
panic = 'unwind'
incremental = false
codegen-units = 16
rpath = false
TESTThe TEST profile is used when you run the cargo build command for benchmarks in debug mode or when you build tests.
[profile.test]
opt-level = 0
debug = 2
debug-assertions = true
overflow-checks = true
lto = false
panic = 'unwind'
incremental = true
codegen-units = 256
rpath = false
BENCHBENCH comes into play when you build benchmarks or build tests with the --release flag.
[profile.bench]
opt-level = 3
debug = false
debug-assertions = false
overflow-checks = false
lto = false
panic = 'unwind'
incremental = false
codegen-units = 16
rpath = false
In Rust, you can create a collection of packages that share a Cargo.lock file, output directory, and other settings such as profiles. This collection is called a workspace and the packages that comprise the workspace are workspace members.
Workspaces are specified in the Cargo.toml file. This is usually done in two ways: using either the root package or virtual manifest.
The root package method involves adding a [workspace] table to your Cargo.toml file. The [workspace] table can be added to a Cargo.toml file where [package] has already been declared if [package] is the root package of the workspace.
By the virtual manifest method, you can create a Cargo.toml file and add a [workspace] table. However, no [package] table will be in the Cargo.toml file. This may be a good option when you want to keep all the packages together in a different directory or when there is no “primary” package.
Below are the most popular Cargo commands. For your convenience, we’ve grouped them into the following categories: build commands, manifest commands, package commands, and publishing commands:
# Build commands
cargo bench \[options\] [benchname]
cargo build [options]
cargo check [options]
cargo clean [options]
cargo doc [options]
cargo fetch [options]
cargo fix [options]
cargo run \[options\] [-- args]
cargo rustc \[options\] [-- args]
cargo rustdoc \[options\] [-- args]
cargo test \[options\] [testname]
# Analysis commands
cargo clippy
# Manifest commands
cargo generate-lockfile [options]
cargo locate-project [options]
cargo metadata [options]
cargo pkgid \[options\] [spec]
cargo tree [options]
cargo update [options]
cargo vendor \[options\] [path]
cargo verify-project [options]
# Package commands
cargo init \[options\] [path]
cargo install [options]
cargo new [options] path
cargo search \[options\] [query...]
cargo uninstall \[options\] [spec...]
# Publishing commands
cargo login \[options\] [token]
cargo owner [options]
cargo package [options]
cargo publish [options]
cargo yank [options] --vers version [crate]
For more information about Cargo commands, check out the official Rust documentation.
Building a Rust application is impossible without Cargo. In addition, you need Cargo commands to publish your project. Put simply, Cargo orchestrates a smooth build, compile, and runtime for your Rust project.
I hope this guide gave you a better understanding of what Cargo is, how it works, and why it’s such a crucial part of any Rust project. If you want to learn more about Cargo, the Rust documentation is a great resource.
Debugging Rust applications can be difficult, especially when users experience issues that are hard to reproduce. If you’re interested in monitoring and tracking the performance of your Rust apps, automatically surfacing errors, and tracking slow network requests and load time, try LogRocket.
LogRocket lets you replay user sessions, eliminating guesswork around why bugs happen by showing exactly what users experienced. It captures console logs, errors, network requests, and pixel-perfect DOM recordings — compatible with all frameworks.
LogRocket's Galileo AI watches sessions for you, instantly identifying and explaining user struggles with automated monitoring of your entire product experience.
Modernize how you debug your Rust apps — start monitoring for free.

Learn how React Router’s Middleware API fixes leaky redirects and redundant data fetching in protected routes.

A developer’s retrospective on creating an AI video transcription agent with Mastra, an open-source TypeScript framework for building AI agents.

Learn how TanStack DB transactions ensure data consistency on the frontend with atomic updates, rollbacks, and optimistic UI in a simple order manager app.

useEffect mistakesDiscover what’s new in The Replay, LogRocket’s newsletter for dev and engineering leaders, in the November 5th issue.
Hey there, want to help make our blog better?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up now