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.toml
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.toml
The 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.
DEV
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
The 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
The 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
BENCH
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 member
s.
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 is like a DVR for web and mobile apps, recording literally everything that happens on your Rust application. 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.
Would you be interested in joining LogRocket's developer community?
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 nowBuild scalable admin dashboards with Filament and Laravel using Form Builder, Notifications, and Actions for clean, interactive panels.
Break down the parts of a URL and explore APIs for working with them in JavaScript, parsing them, building query strings, checking their validity, etc.
In this guide, explore lazy loading and error loading as two techniques for fetching data in React apps.
Deno is a popular JavaScript runtime, and it recently launched version 2.0 with several new features, bug fixes, and improvements […]