原文链接: http://www.sheshbabu.com/posts/rust-module-system/


原文标题: Clear explanation of Rust’s module system


公众号: Rust碎碎念


翻译: Praying

Rust的模块(module)系统相当令人困惑,这也给很多初学者带来了挫败感。

在本文中,我将会通过实际的例子来解释模块系统以便于让你清晰地理解它是怎样工作的并且能够快速在自己的项目中应用。

由于Rust的模块系统比较独特,我希望读者以开放性思维来进行阅读,并且尽量不要将其与其他语言中的模块的工作方式进行比较。

让我们使用下面的文件结构来模拟一个真实世界中的项目:

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

下面是一些使用我们的模块的不同方式:

下面的3个例子应该足以解释Rust的模块系统是如何工作的。

示例1

让我们从第一个例子开始 —— 在main.rs中导入config.rs

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

很多人常犯的第一个错误是因为我们有像config.rshealth_route.rs这样的文件,所以我们就认为这些文件就是模块(module)且可以在其他的文件中将其导入。

下面是从我们的视角(文件系统树(file system tree))看到的内容和从编译器的角度(模块树(module tree))看到的内容:


令人惊奇,编译器只看到了crate模块,也就是我们的main.rs文件。这是因为我们需要显式地在Rust中构建模块树——在文件系统树和模块树之间不存在隐式的转换。

我们需要显式地在Rust中构建模块树——在文件系统树和模块树之间不存在隐式的转换。

想要把一个文件添加到模块树中,我们需要使用mod关键字来将这个文件声明为一个子模块(submodule)。另一件使人们感到困惑的事情是你会认为在相同的文件里把一个文件声明为模块(译者注:比如使用mod关键字把config.rs声明为子模块,你可能认为需要在config.rs里来写声明)。但是我们需要在一个不同文件里进行声明!因为我们在这个模块树里只有main.rs这个文件,所以要在main.rs里将config.rs声明为一个子模块。

mod 关键字声明一个子模块

mod关键字语法如下:

mod my_module;

这里,编译器在相同的目录下查找my_module.rs或者my_module/mod.rs

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

或者

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

因为main.rsconfig.rs在相同的目录下,让我们按照下面的代码声明config模块

// main.rs
+ mod config;

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

这里,我们通过::语法使用print_config函数。
下面是模块树的样子:


我们已经成功地声明了config模块!但是这还不能调用config.rs里的print_config函数。几乎Rust里面的一切默认都是私有(private)的,我们需要使用pub关键字来让这个函数成为公开(public)的:

pub关键字使事物公开

// main.rs
mod config;

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

现在,这样可以正常工作了。我们已经成功的调用了定义在另一个文件里的函数!

示例2

让我们尝试在main.rs中调用定义在routes/health_route.rs里的print_health_route函数。

// main.rs
mod config;

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

正如我们之前所讨论的,我们只能对相同目录下的my_module.rs或者my_module/mod.rs使用mod关键字。
所以为了能够在main.rs中调用routes/health_route.rs里定义的函数,我们需要做下面的事情:

  • 创建一个名为routes/mod.rs的文件并且在main.rs中声明routes子模块
  • routes/mod.rs中声明health_route子模块并且使其成为公开(public)的
  • 使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");
}

下面是模块树的样子:

现在我们可以调用某个目录下文件里定义的函数了。

示例 3

让我们尝试这样的调用main.rs => routes/user_route.rs => models/user_model.rs(译者注:这里是main.rs里调用routes/user_route.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");
}

我们想要在main.rs里调用print_user_route函数,而print_user_route函数调用了print_user_model函数

让我们来进行和之前相同的操作——声明子模块,使函数公开并将子模块添加到mod.rs文件之中。

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");
}

下面是模块树的样子:


等等,我们还没有真正地在print_user_route里调用print_user_model!目前为止,我们仅仅在main.rs里调用定义在其他模块里的函数,在别的文件里调用其他模块的函数应该怎么做么?

如果我们看一下我们的模块树,print_user_model位于crate::models::user_model。所以为了能在非main.rs的其他文件里使用一个模块,我们应该按照模块树中到达指定模块所需要的路径来进行考虑。

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

现在我们已经成功地在一个非main.rs的文件里调用了定义在另一个文件里的函数。

super

如果我们的文件组织包含多级目录,完整的限定名就会变得很长。出于某些原因,我们想要从print_user_route中调用print_health_route。它们分别位于crate::routes::health_routecrate::routes::user_route

我们可以使用完整路径的限定名crate::routes::health_route::print_health_route();, 但是我们也可以使用一个相对路径super::health_route::print_health_route();

模块路径中的super关键字指向父级作用域

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

在上面的例子中,无论是使用完整的限定名还是相对路径的限定名都很冗长。为了让限定名变得更短,我们可以使用use关键字来给路径绑定一个新名字或者别名。

use关键字用于使模块路径更短

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

上面的代码可以重写为:

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

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

除了使用print_user_model这个名字,我们还可以给它起个别名:

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)

添加到Cargo.toml里的依赖对于项目内的所有模块都是可以访问的。我们不需要显式地导入或声明任何东西来使用依赖项。

外部依赖对于项目内的所有模块都是可以访问的

例如,比如说我们在项目中添加了rand[1]这个crate。我们可以像下面这样在代码里直接使用:

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

我们也可以使用use来简化路径:

use rand::random;

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

总结

  • 模块系统是显式的(译者注:需要明确的声明)——不存在和文件系统的1:1映射
  • 我们在一个文件的父级目录把它声明为模块,而不是在文件自身
  • mod关键字用于声明子模块
  • 我们需要显式地将函数、结构体等声明为公开的,这样它们才可以被其他模块访问
  • pub关键字把事物声明为公开的
  • use关键字用于简化(缩短)模块路径
  • 我们不需要显式声明第三方的模块

参考资料

[1]

rand: https://crates.io/crates/rand

【译】关于Rust模块的清晰解释的更多相关文章

  1. 转译符,re模块,random模块

    一, 转译符 1.python 中的转译符 正则表达式中的内容在Python中就是字符串 ' \n ' : \ 转移符赋予了这个n一个特殊意义,表示一个换行符 ' \ \ n' :  \ \  表示取 ...

  2. rust 模块组织结构

    rust有自己的规则和约定用来组织模块,比如一个包最多可以有一个库crate,任意多个二进制crate.导入文件夹内的模块的两种约定方式... 知道这些约定,就可以快速了解rust的模块系统. 先把一 ...

  3. 【译】Rust宏:教程与示例(一)

    原文标题:Macros in Rust: A tutorial with examples 原文链接:https://blog.logrocket.com/macros-in-rust-a-tutor ...

  4. 【译】Rust宏:教程与示例(二)

    原文标题:Macros in Rust: A tutorial with examples 原文链接:https://blog.logrocket.com/macros-in-rust-a-tutor ...

  5. 【译】Rust中的array、vector和slice

    原文链接:https://hashrust.com/blog/arrays-vectors-and-slices-in-rust/ 原文标题:Arrays, vectors and slices in ...

  6. 【译】Rust,无畏并发

    原文链接:https://dev.to/imaculate3/fearless-concurrency-5fk8 > 原文标题:That's so Rusty! Fearless concurr ...

  7. python文档自译:os模块-01【说明部分】

    15.1. os - Miscellaneous operating system interfaces This module provides a portable way of using op ...

  8. 【腾讯Bugly干货分享】微信mars 的高性能日志模块 xlog

    本文来自于腾讯bugly开发者社区,未经作者同意,请勿转载,原文地址:http://dev.qq.com/topic/581c2c46bef1702a2db3ae53 Dev Club 是一个交流移动 ...

  9. Python os与sys模块解析

    os与sys模块的官方解释如下: os: This module provides a portable way of using operating system dependent functio ...

随机推荐

  1. 达梦产品技术支持-DM8-数据库安装

    (该文档只适合个人环境搭建,未涉及到数据库的各种参数配置,未涉及到数据库规划,若需要企业环境搭建请咨询专业人员) 基于Windows的安装 windows下安装是图形化界面,与linux下的图形化界面 ...

  2. Windows7 提示“无法访问 xxxx,您没有权限访问,请与网络管理员联系请求访问权限”的解决办法

    Windows7 客户端访问提示"无法访问 xxxx,您没有权限访问,请与网络管理员联系请求访问权限"的解决办法

  3. BUUCTF-[极客大挑战 2019]BabySQL 1 详解

    打开靶机 应该是love sql惹的事吧,来了个加强版本的sql注入,不过我们先输入账号密码看有什么反应 整一手万能密码,闭合双引号?username=admin&password=admin ...

  4. shell-整数测试多范例多生产案例举例

    1. 整数测试举例范例1:整数条件测试举例 root@test-1 ~]# a1=10;a2=13 [root@test-1 ~]# echo $a1 $a2 10 13 [root@test-1 ~ ...

  5. ansible-任务控制tags

    1. ansible-任务控制tags介绍        如果你有一个大型的剧本,那么只能运行它的特定部分而不是在剧本中运行所有内容可能会很有用.因此,Ansible支持"tags:&quo ...

  6. day55 Pyhton 前端Jquery07

    昨日回顾: 表单,点击submit提交以后,服务端受到信息 import socket import pymysql from urllib.parse import unquote def run( ...

  7. composer 阿里云镜像配置

    https://developer.aliyun.com/composer 全局配置(推荐) 所有项目都会使用该镜像地址: composer config -g repo.packagist comp ...

  8. MySQL锁详细讲解

    本文章向大家介绍MySQL锁详细讲解,包括数据库锁基本知识.表锁.表读锁.表写锁.行锁.MVCC.事务的隔离级别.悲观锁.乐观锁.间隙锁GAP.死锁等等,需要的朋友可以参考一下   锁的相关知识又跟存 ...

  9. 全文检索Solr集成HanLP中文分词【转】

    以前发布过HanLP的Lucene插件,后来很多人跟我说其实Solr更流行(反正我是觉得既然Solr是Lucene的子项目,那么稍微改改配置就能支持Solr),于是就抽空做了个Solr插件出来,开源在 ...

  10. Anderson《空气动力学基础》5th读书笔记导航

    没错,在2018年,我正式启程了安德森教授这本空气动力学圣经的阅读,为了深入理解概念,特写此刊,边读边写,2020年一定写完,写不完我就/¥@%¥---! 以下是导航: 第一章任务图: 第一章思维导图 ...