Ukpai Ugochi I'm a full-stack JavaScript developer on the MEVN stack. I love to share knowledge about my transition from marine engineering to software development to encourage people who love software development and don't know where to begin. I also contribute to OSS in my free time.

Demystifying Cargo in Rust

4 min read 1373

Rust Cargo Demystifying Nocdn

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 in Rust?

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.

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 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.

What is 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.

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 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                       

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 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.

Cargo commands

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.

Conclusion

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.


More great articles from LogRocket:


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 web frontends for Rust apps

Debugging 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 and mobile 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 — .

Ukpai Ugochi I'm a full-stack JavaScript developer on the MEVN stack. I love to share knowledge about my transition from marine engineering to software development to encourage people who love software development and don't know where to begin. I also contribute to OSS in my free time.

Leave a Reply