Make the Rust module system clear


The Rust module system is very confusing and makes novices feel a great sense of failure

In this article, I will explain the modular system with practical examples. You will clearly understand how it works and can be applied to your project immediately

Since the Rust module system is very unique, I ask you to open your mind when reading this article and not apply it to other languages

Simulate a real project with the following structure:

my_project
├── Cargo.toml
└─┬ src
  ├── main.rs
  ├── config.rs
  ├─┬ routes
  │ ├── health_route.rs
  │ └── user_route.rs
  └─┬ models
    └── user_model.rs

There are different ways for us to use module

These 3 examples should be sufficient to explain how Rust's module system works.

Three examples should be sufficient to solve how the Rust module system works

Example 1

// main.rs
fn main() {
  println!("main");
}
// config.rs
fn print_config() {
  println!("config");
}

The first error is due to some files config.rs, health_route.rs, etc. we think these files are modules, and we can import them into other files

Below are the file system tree I see and the module tree seen by the compiler

Surprisingly, the compiler can only see the crite module, which is also the main.rs file. This is because we need to display the build module tree in Rust - there is no implicit mapping between the file system tree and the module tree

We need to show that building the Rust module tree has no implicit mapping to the file system

In order to add the file to the module tree, we need to declare the file as a sub module using the mod keyword. The following may also be confusing. You should assume that we declare the file as a module in the same file. But we declare it in different files. Since we only have main.rs in the module tree, let's declare config.rs as a sub module of main.rs

mod keyword declaration sub module

The syntax of the keyword mod is
mod my_module

The compiler below looks for my in the same directory_ Module.rs or my_module/mod.rs

my_project
├── Cargo.toml
└─┬ src
  ├── main.rs
  └── my_module.rs

or

my_project
├── Cargo.toml
└─┬ src
  ├── main.rs
  └─┬ my_module
    └── mod.rs

Since main.rs and config.rs are in the same directory, let's declare config as a module

// main.rs
+ mod config;

fn main() {
+ config::print_config();
  println!("main");
}
// config.rs
fn print_config() {
  println!("config");
}

We access the function print_config:: Syntax

Now the module tree is like this

We successfully declared the config module! However, print in config.rs cannot be called yet_ Config(). Almost everything in Rust is private by default. We need to use the keyword pub to make the function public

The keyword pub can make access public

// main.rs
mod config;

fn main() {
  config::print_config();
  println!("main");
}
// config.rs
- fn print_config() {
+ pub fn print_config() {
  println!("config");
}

Now, with success, I succeeded in calling the defined functions in different files.

Example 2

Let's call routes/health_ from main.rs. Print in route.rs_ health_ route().

// main.rs
mod config;

fn main() {
  config::print_config();
  println!("main");
}
// routes/health_route.rs
fn print_health_route() {
  println!("health_route");
}

As mentioned earlier, using the keyword mod is just my_module.rs or my_module/mod.rs should be in the same directory

So in order to call routes/health_ from main.rs For the function in route.rs, we need to do the following:

  • Create a file named routes/mod.rs, and define the routes sub module in main.rs
  • Define the sub module health in routes/mod.rs_ Route and make its access rights public
  • Make health_ The access permission in route.rs is public
my_project
├── Cargo.toml
└─┬ src
  ├── main.rs
  ├── config.rs
  ├─┬ routes
+ │ ├── mod.rs
  │ ├── health_route.rs
  │ └── user_route.rs
  └─┬ models
    └── user_model.rs
// main.rs
mod config;
+ mod routes;

fn main() {
+ routes::health_route::print_health_route();
  config::print_config();
  println!("main");
}
// routes/mod.rs
+ pub mod health_route;// routes/mod.rs
+ pub mod health_route;
// routes/health_route.rs
- fn print_health_route() {
+ pub fn print_health_route() {
  println!("health_route");
}

Below is what the module tree looks like

You can now call the functions in the files in the folder

Example 3

Let's try calling from main. Rs = > routes / user_ route.rs => models/user_ model.rs

// main.rs
mod config;
mod routes;

fn main() {
  routes::health_route::print_health_route();
  config::print_config();
  println!("main");
}

// routes/user_route.rs
fn print_user_route() {
  println!("user_route");
}
// models/user_model.rs
fn print_user_model() {
  println!("user_model");
}

We want to call print_ from main. user_ Route then calls print_ user_ Functions in model

Let's do the previous again - define the sub module, modify the function access permission to common, and then add the mod.rs file

my_project
├── Cargo.toml
└─┬ src
  ├── main.rs
  ├── config.rs
  ├─┬ routes
  │ ├── mod.rs
  │ ├── health_route.rs
  │ └── user_route.rs
  └─┬ models
+   ├── mod.rs
    └── user_model.rs
// main.rs
mod config;
mod routes;
+ mod models;

fn main() {
  routes::health_route::print_health_route();
+ routes::user_route::print_user_route();
  config::print_config();
  println!("main");
}
// routes/mod.rs
pub mod health_route;
+ pub mod user_route;
// models/mod.rs
+ pub mod user_model;
// models/user_model.rs
- fn print_user_model() {
+ pub fn print_user_model() {
  println!("user_model");
}

The module tree now grows like this

Wait, we can't actually print yet_ user_ Calling print_ in route user_ model! So far, we have just called functions defined in other modules from main.rs. What should we do in other files?

If I look at the module tree, the function print_ user_ The model is in crite:: models:: user_ So in order to use the modules in the file, not main.rs. we should think about accessing them according to the module path in the necessary module tree

// routes/user_route.rs
pub fn print_user_route() {
+ crate::models::user_model::print_user_model();
  println!("user_route");
}

We successfully invoked the functions defined in other files in non main.rs files.

super

If our organization file has multiple directories, the reference to the complete name is too long. If for some reason, we want to start from print_ user_ Calling print_ in route health_ Route. Will have the following two paths: crite:: routes:: health_ Route and crite:: routes:: user_ route.

We call it by using the full reference name crite:: routes:: health_ route::print_ health_ Route(), but we can also use the relative path super::health_route::print_health_route(). Note that super is used to point to the parent scope

The keyword super of the module path specifies the parent scope

pub fn print_user_route() {
  crate::routes::health_route::print_health_route();
  // can also be called using
  super::health_route::print_health_route();

  println!("user_route");
}

use

In the above example, using the full reference name or even the relative reference name is lengthy. In order to make the reference name shorter, we use the keyword use to bind the new module name or rename it

The keyword use is used to shorten the module path

pub fn print_user_route() {
  crate::models::user_model::print_user_model();
  println!("user_route");
}

The above code can be refactored into

use crate::models::user_model::print_user_model;

pub fn print_user_route() {
  print_user_model();
  println!("user_route");
}

Replace with print_user_model name, which can be renamed

use crate::models::user_model::print_user_model as log_user_model;

pub fn print_user_route() {
  log_user_model();
  println!("user_route");
}

Extension module

Add dependencies to Cargo.toml, and all modules are available in the project. There is no need to display, introduce or declare anything else to use dependencies

Extension dependencies are globally available for modules throughout the project
For example, by adding Rand slate to the project, we can use it directly in the code

pub fn print_health_route() {
  let random_number: u8 = rand::random();
  println!("{}", random_number);
  println!("health_route");
}

You can also use use to shorten the reference path

use rand::random;

pub fn print_health_route() {
  let random_number: u8 = random();
  println!("{}", random_number);
  println!("health_route");
}

summary

  • Module systems are shown referenced - there is no relationship between one-to-one and file system mapping
  • The declaration file is in his father as a module, not in himself
  • The keyword mod is used to declare the submodule
  • It is required to display and declare that the access rights of functions, structures, etc. are public, and all of them can be in other modules
  • The keyword pub makes the code access permission common
  • Third party modules do not need to be declared

Keywords: Rust

Added by blawson7 on Tue, 23 Nov 2021 04:01:43 +0200