#2 Rust - Cargo Package Manager


Diving Into Rust - Understanding Cargo and Dependencies

Hey there 👋

In the previous post, we got an introduction to Rust and how to install Rust in our system.

We also saw that cargo is at the core of setting up and managing a Rust project. In this post, we will dive deeper into cargo and understand why we need dependencies for projects.

Suppose you wanted to write a Rust program that would generate random numbers and then use these numbers for some subsequent functionality.

There are two ways to go about this - write all the functions from scratch, and take longer time to ship a working project OR use pre-built functions and packages to make our work easier and save time.

The second approach makes sense, doesn’t it? Because our core focus should be on building new functionalities, and not reinventing the wheel!

And that’s exactly why we need dependencies in projects.

Someone has already written the necessary functions and packaged it in an industry-standard manner, and it is only wise that we utilise these packages.

Now, there has to be a place where all these packages come from. Similar to npmjs registry, where all node packages are registered, stored and retrieved, Rust also has something called crates.io where many helpful packages and dependencies are registered.

Image description

This registry is very useful, and as you can see, there are thousands of “crates” available. Crates are nothing but packages and dependencies. If you are familiar with other languages where such package management is used, then using Crates.io should be fairly straightforward.

Let us take an example package here. Following the Rust documentation site, we need to install a package called ferris-says. Now, there are two ways to install packages to your Rust project -

1. Using Cargo -

Open the terminal in your project directory run the command cargo add package_name where package_name is the name of the package you need from Crates registry.

Image description

2. Adding configuration line in Cargo.toml -

Under the [dependencies] header, we just add the name of the package and specify the version number, like this on line number 9-

Image description

For this example, let’s go with the command line option and see what happens -

Image description

In a flash of a second, the operation is done, and the package is added!

The CLI gives a very crisp log of the sequence as the package installation happens. It first says that the cargo is updating the crates.io index (retrieval of package), and then adds ferris-says to dependencies. In the middle of this, it also displays the Features that were added with the package - something called clippy - which is a crate that contains a list of lints that are not available in the inbuilt Rust compiler lint list. These lints help improve our cargo.

And finally, it displays the final log that the crates.io index has been updated and we can immediately see that in the Cargo.toml file, under [dependencies] header, the crate name with the version number is added.

What is Cargo.lock in Rust?

As you can see in the file structure, as soon as you ran the project, Rust “builds” the project, indexing all the dependencies we just configured in Cargo.toml file, and then generates a Cargo.lock file. This file contains a more detailed version of the Cargo.toml file and is meant to be used when we deploy our Rust app in different environments.

Image description

Few things to note about the Cargo.lock file -

  • As it mentions at the top, this file is not intended for manual editing unless absolutely necessary and if you have enough knowledge to play around with it.
  • Also, this file is automatically generated by Cargo, so even if you delete this file, there is nothing to worry; simply running the app again will ensure that this file is generated again.
  • It can be seen that the file contains very detailed information about the app and various dependencies including names, version numbers, and checksum codes. This ensures that the app and its dependencies are secure and that it works seamlessly across different production environments as well.

Advantage of manually adding the dependencies

If you noticed, there is a tab in the Crates.io website in the ferris-says package page, called “6 versions”. This means that from the time of its release in the Crates registry to the present moment, there have been 6 versions of the ferris-says released. And when you install it using the cargo add command, it adds the latest version by default.

Image description

In case you wanted to add a specific version or rollback to a previous version of the package for various reasons, you can do so by simply editing the package name in the Cargo.toml file under [dependencies] header.

Image description

This applies to not only editing the installed packages names, but also adding new packages manually with the desired version numbers, and running the app with the command cargo run. This will ensure that new packages with the exact specified version are installed, and also modifies the versions for existing packages in the Cargo.lock file as well, and then outputs the result -

Image description

You can verify whether the version change has taken place by checking the Cargo.lock file too -

Image description

You can see here that the package version number has changed from 0.3.1 to 0.2.1 now.

How to use packages in Rust?

Until now, we saw how to install or modify packages in Rust. Now to actually use them in our application code, we need to specify in our main.rs file or any other Rust file that we intend to “use” the package, and also what functions specifically from the package we want to use.

Since we are learning through the documentation, let’s go with the given example in the docs. And, remember, the docs use the latest versions so we have to change our ferris_says version back to the latest one.

This is the example code from the docs -

Image description

As you can see, the first line says use ferris_says::say - where use is the keyword to specify that we want to use a package, and then the package name ferris_says, followed by the double colon :: which is also known as the “path separator” and finally the name of the function we want to use from the package, namely say. When we run this program with cargo run, we get the following beautiful output -

Image description

CLI outputs are so pleasing to look at, aren’t they 😁

Do not worry about the syntax or many of the keywords yet! We are doing to dive into these things very soon.

Until then, keep yourself Rusty 🦀