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:
- What is Cargo?
- How Cargo works
- What is
- Cargo and profiles
- Cargo and workspaces
- Cargo commands
What is Cargo in Rust?
Cargo is Rust’s build system and package manager. With this tool, you’ll get a repetitive 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.
How Cargo works
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
The function of the
cargo.lock file is to allow for a repetitive build. Therefore, there should be no cases of different builds, even when you share your project.
You may be wondering how the
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.
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
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
The first thing to note about
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 concurrent 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 and profiles
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
[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:
release. The command running depends on the profile to be chosen. You can change compiler settings in the
[profile] table of your
Let’s look at the default profiles as well as the commands and command-line flags associated with each.
When 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
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
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
BENCH comes into play when you build benchmarks or build tests with the
[profile.bench] opt-level = 3 debug = false debug-assertions = false overflow-checks = false lto = false panic = 'unwind' incremental = false codegen-units = 16 rpath = false
Cargo and workspaces
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
Workspaces are specified in the
cargo.toml file. This is usually done in two ways: using either the
root package or
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.
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] # 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.
LogRocket: Full visibility into production Rust appsDebugging Rust applications can be difficult, especially when users experience issues that are difficult to reproduce. If you’re interested in monitoring and tracking performance of your Rust apps, automatically surfacing errors, and tracking slow network requests and load time, try LogRocket.
LogRocket is like a DVR for web apps, recording literally everything that happens on your Rust app. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred. LogRocket also monitors your app’s performance, reporting metrics like client CPU load, client memory usage, and more.
Modernize how you debug your Rust apps — start monitoring for free.