In every programming language, there is something equally important - the package manager. Take JavaScript for example, it is already familiar with npm - a tool that allows you to download and share many useful libraries. Here, developers contribute and use packages to accelerate the product development process.
In Rust, cargo is a tool that functions similarly to npm, it also allows sharing and downloading packages, but the usage is somewhat different. Therefore, in today's article, we will explore the basic components, modules, and the cargo package manager together.
Rust provides two concepts, Crates and Packages, to unify the understanding. Simply put, a Crate is a single source file, while Packages are collections of multiple Crates.
cargo
is the package manager integrated into Rust, it has similar functionality to Node's npm. This means that we can easily create, add, and remove packages.
The syntax to create a package:
$ cargo new my-project
A directory my-project
is created, let's see what's inside it.
$ ls my-project
Cargo.toml
src
The src
directory contains a main.rs
file, which is the place for executable code. Cargo.toml
stores various information about my-project
such as its name, dependencies, similar to package.json
in a JS/Node project.
To run the project, we use the command cargo run
.
$ cargo run
Compiling my-project v0.1.0 (/Users/hoaitx/my-project)
Finished dev [unoptimized + debuginfo] target(s) in 0.81s
Running `target/debug/my-project`
Hello, world!
In JavaScript or Node.js, we can import/export modules using familiar syntax such as import
/export
or require
, separating related logic into separate files for reusability. Rust has a similar concept, but with some significant differences.
To understand it simply, let's consider an .rs
file as a module in Rust. When declaring a module mymodule
in src/main.rs
, it means that we are "declaring" to Rust that I will use this module...
pub mod mymodule;
fn main() {
...
}
In this case, Rust will search for your mymodule
in the following locations:
Suppose we write code for mymodule
, let's go through each method.
pub mod mymodule {
pub fn hello() {
println!("Hello, world!");
}
}
fn main() {
mymodule::hello();
}
File src/mymodule.rs:
pub fn hello() {
println!("Hello, world!");
}
File src/main.rs
pub mod mymodule;
fn main() {
mymodule::hello();
}
This declaration method may seem unfamiliar compared to regular JS/Node, where you export what you need and import what you use. In Rust, modules are "declared" for use, and Rust will automatically search for modules through the specified locations mentioned above.
We can also declare a module within another module. For example, adding a sub-module mysubmodule
to src/mymodule.rs
:
pub mod mysubmodule {
pub fn sub_hello() {
println!("Hello, world!");
}
}
pub fn hello() {
println!("Hello, world!");
}
Then in src/main.rs
, we can use:
pub mod mymodule;
fn main() {
mymodule::hello();
mymodule::mysubmodule::sub_hello();
}
If you notice, you will see that the syntax to access sub-modules is ::
, so the deeper the sub-modules, the longer the path. To simplify the code, Rust provides the use
syntax to reduce code duplication.
In the file src/main.rs
:
pub mod mymodule;
use mymodule::mysubmodule;
fn main() {
mymodule::hello();
mysubmodule::sub_hello();
}
use mymodule::mysubmodule
has shortened the syntax for calling sub_hello()
, and if you want to further shorten it, you can continue accessing sub-modules inside the use
statement.
Modules can be declared using absolute or relative paths. Absolute paths start with crate
, while relative paths do not. When using absolute paths, its starting position is from src
, while with relative paths, it starts directly from the usage position.
mod front_of_house {
pub mod hosting {
fn add_to_waitlist() {}
}
}
pub fn eat_at_restaurant() {
// absolute path
crate::front_of_house::hosting::add_to_waitlist();
// relative path
front_of_house::hosting::add_to_waitlist();
}
In the eat_at_restaurant
function, there are two ways to call the add_to_waitlist
function in the front_of_house
modules. While the absolute path requires the additional crate
declaration, making the code slightly longer compared to the relative path that starts directly from calling into the front_of_house
modules. The choice between absolute and relative paths depends on each individual's workflow.
In relative paths, there is also the super
syntax to "step back" and call a function in another module.
fn deliver_order() {}
mod back_of_house {
fn fix_incorrect_order() {
cook_order();
super::deliver_order();
}
fn cook_order() {}
}
One interesting thing about modules in Rust is that modules declared from the beginning can be accessed throughout the project in different places. It's different from the import/export module syntax you might have seen in other programming languages. For example, in JavaScript, to call the same module in multiple places or files, we need to import it multiple times. But not in Rust.
To make it easier to understand, I will create another module named myothermodule
in src/myothermodule.rs
, and immediately we can call a function in src/mymodule.rs
using super
.
# file src/myothermodule.rs
pub fn call_mymodule() {
super::mymodule::hello();
}
crates.io is the place to aggregate shared packages. You can search, read documentation, download, and use most shared packages here. For example, I want to use a package named rand
above. There are two ways to install it:
cargo add rand
. Cargo.toml
and add the name and version of the package under [dependencies]
. Then use the cargo fetch
command if your code editor does not automatically download the newly added package.To use these packages, we don't need to declare modules but can directly use use
. For example, calling a function that generates a random number in the range 1-100.
use rand::Rng;
fn main() {
let secret_number = rand::thread_rng().gen_range(1..=100);
}
5 profound lessons
Every product comes with stories. The success of others is an inspiration for many to follow. 5 lessons learned have changed me forever. How about you? Click now!
Subscribe to receive new article notifications
Hello, my name is Hoai - a developer who tells stories through writing ✍️ and creating products 🚀. With many years of programming experience, I have contributed to various products that bring value to users at my workplace as well as to myself. My hobbies include reading, writing, and researching... I created this blog with the mission of delivering quality articles to the readers of 2coffee.dev.Follow me through these channels LinkedIn, Facebook, Instagram, Telegram.
Comments (0)