Clear explanation of Rust’s module system

栏目: IT技术 · 发布时间: 4年前

内容简介:Rust’s module system is surprisingly confusing and causes a lot of frustration for beginners.In this post, I’ll explain the module system using practical examples so you get a clear understanding of how it works and can immediately start applying this in y

Rust’s module system is surprisingly confusing and causes a lot of frustration for beginners.

In this post, I’ll explain the module system using practical examples so you get a clear understanding of how it works and can immediately start applying this in your projects.

Since Rust’s module system is quite unique, I request the reader to read this post with an open mind and resist comparing it with how modules work in other languages.

Let’s use this file structure to simulate a real world project:

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

These are the different ways we should be able to consume our modules:

Clear explanation of Rust’s module system

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

Example 1

Let’s start with the first example - importing config.rs in main.rs .

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

The first mistake that everyone makes is just because we have files like config.rs , health_route.rs etc, we think that these files are modules and we can import them from other files.

Here’s what we see (file system tree) and what the compiler sees (module tree):

Clear explanation of Rust’s module system

Surprisingly, the compiler only sees the crate module which is our main.rs file. This is because we need to explicitly build the module tree in Rust - there’s no implicit mapping between file system tree to module tree.

We need to explicitly build the module tree in Rust, there’s no implicit mapping to file system

To add a file to the module tree, we need to declare that file as a submodule using the mod keyword. The next thing that confuses people is that you would assume we declare a file as module in the same file. But we need to declare this in a different file! Since we only have main.rs in the module tree, let’s declare config.rs as a submodule in main.rs .

The mod keyword declares a submodule

The mod keyword has this syntax:

mod my_module;

Here, the compiler looks for my_module.rs or my_module/mod.rs in the same directory.

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 the config module as follows:

// main.rs
+ mod config;

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

We’re accessing the print_config function using the :: syntax.

Here’s how the module tree looks like:

Clear explanation of Rust’s module system

We’ve successfully declared the config module! But this is not sufficient to be able to call the print_config function inside config.rs . Almost everything in Rust is private by default, we need to make the function public using the pub keyword:

The pub keyword makes things public
// main.rs
mod config;

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

Now, this works. We’ve successfully called a function defined in a different file!

Example 2

Let’s try calling the print_health_route function defined in routes/health_route.rs from main.rs .

// main.rs
mod config;

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

As we discussed earlier, we can use the mod keyword only for my_module.rs or my_module/mod.rs in the same directory.

So in order to call functions inside routes/health_route.rs from main.rs , we need to do the following things:

  • Create a file named routes/mod.rs and declare the routes submodule in main.rs
  • Declare the health_route submodule in routes/mod.rs and make it public
  • Make the functions inside health_route.rs 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/health_route.rs
- fn print_health_route() {
+ pub fn print_health_route() {
  println!("health_route");
}

Here’s how the module tree looks like:

Clear explanation of Rust’s module system

We can now call a function defined in a file inside a 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 the function print_user_model from print_user_route from main .

Let’s make the same changes as before - declaring submodules, making functions public and adding 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;
// routes/user_route.rs
- fn print_user_route() {
+ pub fn print_user_route() {
  println!("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");
}

Here’s how the module tree looks like:

Clear explanation of Rust’s module system

Wait, we haven’t actually called print_user_model from print_user_route ! So far, we’ve only called the functions defined in other modules from main.rs , how do we do that from other files?

If we look at our module tree, the print_user_model function sits in the crate::models::user_model path. So in order to use a module in files that are not main.rs , we should think in terms of the path necessary to reach that module in the module tree.

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

We’ve successfully called a function defined in a file from a file that’s not main.rs .

super

The fully qualified name gets too lengthy if our file organization is multiple directories deep. Let’s say for whatever reason, we want to call print_health_route from print_user_route . These are under the paths crate::routes::health_route and crate::routes::user_route respectively.

We can call it by using the fully qualified name crate::routes::health_route::print_health_route() but we can also use a relative path super::health_route::print_health_route(); . Notice that we’ve used super to refer to the parent scope.

The super keyword in module path refers to 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

It would be tedious to use the fully qualified name or even the relative name in the above examples. In order to shorten the names, we can use the use keyword to bind the path to a new name or alias.

The use keyword 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 as:

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

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

Instead of using the name print_user_model , we can also alias it to something else:

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

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

External modules

Dependencies added to Cargo.toml are available globally to all modules inside the project. We don’t need to explicitly import or declare anything to use a dependency.

External dependencies are globally available to all modules inside a project

For example, let’s say we added the rand crate to our project. We can use it in our code directly as:

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

We can also use use to shorten the path:

use rand::random;

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

Summary

mod
pub
use

Thanks for reading! Feel free to follow me in Twitter for more posts like this :)


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Elements of Information Theory

Elements of Information Theory

Thomas M. Cover、Joy A. Thomas / Wiley-Blackwell / 2006-7 / GBP 76.50

The latest edition of this classic is updated with new problem sets and material The Second Edition of this fundamental textbook maintains the book's tradition of clear, thought-provoking instr......一起来看看 《Elements of Information Theory》 这本书的介绍吧!

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

在线进制转换器
在线进制转换器

各进制数互转换器

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具