Rust Modules: A Comprehensive Guide to Packages and Best Practices

Module In Rust program

Modules in Rust Program help in rending a program into logical units for better readability and organisation.

Once a program turn larger, it is significant to divide it into multiple files or namespaces. Modules help in structuring to our program.

A module is a gathering of items: functions, structs and also other modules.

Defining a Module in Rust Program

The mod keyword is used the define a module. The syntax of module is below:

// syntax of a module
mod module_name {
  // code
}

Above, module_name is the name of module

Please now, let’s define a module.

// a module named config
mod config {
    // a function print inside of the module 
    fn print() {
        println!("config!");
    }
}

The above example, you make a module named config using the mod keyword.

Under the module you can define multiple items. Below, you have defined the print() function.

Visibility of The Items inside a Module in Rust program

Inside Items a module can be private or public. By the default, a module is private. that means items inside the module cannot be appreciated outside of the module.

The pub keyword can be used to given an item public assignation.

Now see an example-

mod config {
    // items in modules by default have private visibility
    fn select() {
        println!("called config::select");
    }

    // use the `pub` keyword to override private visibility
    pub fn print() {
        println!("called config::print");
    }
}

Above, you define a module named config with the two functions select() and print().

Supposing you compile the above program, you don’t feedback any output because you have not used the functions yet.

warning: function `select` is never used
 --> src/lib.rs:3:8
  |
3 |     fn select() {
  |        ^^^^^^
  |
  = note: `#[warn(dead_code)]` on by default

warning: function `print` is never used
 --> src/lib.rs:8:12
  |
8 |     pub fn print() {
  |            ^^^^^

Now, let’s call the functions under the module.

mod config {
    // items in modules by default have private visibility
    fn select() {
        println!("called config::select");
    }

    // use the `pub` keyword to override private visibility
    pub fn print() {
        println!("called config::print");
    }
}

fn main() {
    // public items inside module can be accessed outside the parent module
    // call public print function from display module
    config::print();
}

Output :

called display::print

Below, you call the public function print() inside of config module using the syntax config::print(). The :: operator is used to individual the module name and the item to call inside the module.

Still, private items inside of the module are not get-at-able outside the module. Suppose you call the private function select() inside the config module, you can get a compilation error.

mod config {
    // items in modules by default have private visibility
    fn select() {
        println!("called config::select");
    }

    // use the `pub` keyword to override private visibility
    pub fn print() {
        println!("called config::print");
    }
}

fn main() {
    // private items inside module cannot be accessed outside the parent module
    // calling private select function inside config module will cause a compilation error
    display::select();
}

Error:

error[E0603]: function `select` is private
  --> src/main.rs:16:14
   |
16 |     display::select();
   |              ^^^^^^ private function

Above error mentions that the function select is private. this, visibility of the items inside a module is an important design discretion.

Example: Using Module in Rust Program

mod player {
    // private function
    fn focus() {
        println!("called player::focus");
    }

    // public function
    pub fn shift() {
        println!("called player::shift");
    }

    // public function
    pub fn jump() {
        // call private function focus and shift inside the module
        focus();
        shift();
        println!("called player::jump");
    }
}

fn main() {
    // call public function jump from player module
    player::jump();
}

Output :

called player::focus
called player::shift
called player::jump

Below, we identify multiple functions inside the player module. Note that we are capable to call the private function focus() in other function jump() inside the same module.

Nested Modules In Rust

A module can be defined inside other module. Now This is known as module nesting.

Now see an example-

// nested module
pub mod player {
    pub mod sprite {
        pub fn create() {
            println!("called player::sprite::create");
        }
    }
}

fn main() {
    // call public function create from sprite module which is inside player module 
    player::sprite::create();
}

Output :

called player::sprite::create

Above, you have a sprite module shelter within the player module.

You define a public function create() inside of the sprite module what is called using player::sprite::create() outside the module in the main() function.

Keyword use in Rust Program

You can use the use keyword to get items inside a module into the present scope. The use keyword assistance us extract writing out the full module path to call functions.

Now let’s rewrite our nested module example by help of the use keyword.

// nested module
pub mod player {
    pub mod sprite {
        pub fn create() {
            println!("called player::sprite::create");
        }
    }
}

// bring the create function into scope
use player::sprite::create;

fn main() {
    // call public function directly
    create();
}

Output :

called player::sprite::create

Above, you use the use keyword to get the create() function into the present scope from the sprite module which is the inside the player module. It’s allow us to call the create() function presently, without having to fully determine the name as player::sprite::create().

Crate and Package In Rust Program

A crate can contain one or more than Rust modules, which in turn can comprise code, for example functions, types, and constants.

A crate is of two types:-

  • Binary crate
  • Library crate

A binary crate is a Rust program that compiles to an executable or multiplex executables and has a main() function for every executable.

A library crate doesn’t compose to an executable and doesn’t have a main() function. A library crate usually defines a apportion functionality that can be used in multiple projects

Creating a Package in Rust Program

You can use cargo to create a package. A package comprise one or more crates that take measures a set of functionality.

For create a binary package, you can use the cargo command in the terminal.

$ cargo new hello_world --bin

Output :

Created binary (application) `hello_world` package

You make a binary package hello_world using cargo and the --bin option. It is the default cargo conduct.

Now let’s look at the object of the hello_world package.

hello_world
├── Cargo.toml
└── src
    └── main.rs

Below,

  • hello_world_lib is the package prescriptive
  • Cargo.toml is a file which comprise metadata about the crate, for example its name, version, and dependencies
  • src/lib.rs is crate root and comprise the source code of the library package

A package can comprise src/main.rs also src/lib.rs. In this case, it has been two crates: a binary and a library, both of with the same name as the package. Now for a example-

hello_world
├── Cargo.toml
└── src
    └── lib.rs
    └── main.rs

Note: Cargo by symposium passes the crate root files to the Rust Program compiler to build the library or binary.

Rust Cargo Package Manager

Cargo is the Rust Program package manager. It appears pre-installed with Rust and can be used to package dependency, manage them as well as construct and give away our own packages/libraries.

The Features of Cargo in Rust Program

Cargo is a order line tool for Rust which comes with this features:

  • Dependency management In Rust

Cargo does it easy to manage the dependencies of your project, together with adding, updating, and removing dependencies. In Rust

  • Building and packaging In Rust

Cargo can do automatically construct and package your Rust projects, making binary or library code that can be apportion with others.

  • Document generation In Rust

Cargo can do automatically produce documentation for your code, creating it easier for several developers to understand and use our library.

  • Download crates In Rust

Cargo can download and install library from crates.io, that is a central assortment for Rust crates.

  • Run a binary or tests In Rust

Cargo can construct our source code, move the executable binary and also run our tests.

Dependency Management with Cargo in Rust Program

One of the elementary features of cargo is that it can download, conduct external libraries.

Beginning by creating a Rust project using cargo-

$ cargo new hello_world

Now,

Cargo.toml file in the original of our project directory hello_world is used to conduct the dependencies.

If we want to use the “rand” make, we add the following line to the [dependencies] article of the Cargo.toml .

[package]
name = "hello_world"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
rand = "0.8.5"
  • After, we’ll need to import the make in our src/main.rs Rust file. We can do it’s by using extern crate <crate_name> line at the head of the file.
extern crate rand;
  • Now, we can use the “rand” make to produce a random number between 1 and 6. The use keyword is used to here to import items (such as functions, types, and constants) from the “rand” crate in the present scope.
extern crate rand;
use rand::Rng;

fn main() {
    let mut rng = rand::thread_rng();

    // simulate rolling a die
    println!("roll = {}", rng.gen_range(1..=6));
}

# Output: roll = 5

Building and Running Project with Cargo in Rust Program

You can use cargo to install, construct and run our hello_world project with the “rand” crate. Now see how below-

Build in the project

$ cargo build

Output:

Compiling libc v0.2.139
Compiling cfg-if v1.0.0
Compiling ppv-lite86 v0.2.17
Compiling getrandom v0.2.8
Compiling rand_core v0.6.4
Compiling rand_chacha v0.3.1
Compiling rand v0.8.5
Compiling hello_world v0.1.0 (/experiments/rust-practice/hello_world)
  Finished dev [unoptimized + debuginfo] target(s) in 2.57s

The cargo build command at first installs any crates that’s in use inside the src/ directory and then proceeds to compose the project.

Run the project In Rust

$ cargo run

Output :

Finished dev [unoptimized + debuginfo] target(s) in 0.05s
    Running `target/debug/hello_world`
roll = 5

Cargo Commands Useful in Rust

Cargo can do a cluster of Iterative tasks for us. Here are some of the same used cargo commands.

Screenshot-2024-01-24-063140
Previous
Rust Error Troubleshooting Comprehensive Guide to Memory Management
Next
Advanced Rust Topics: Dive Deeper into the Language's Capabilities