写给前端看的Rust教程(11)Module

1,279 阅读5分钟

原文:24 days from node.js to Rust

前言

node.js发布之初的突出特性之一就是使用起来非常简单,想要运行一个JavaScript文件只需使用node file.js命令即可,想要引入一个本地模块只需("./module.js")即可。如果require()不是相对或绝对路径,那么node将在node_modules文件夹中查找。曾经那是一段美好的时光,当然,如果你是近几年才接触node.js的,那么您可能对它是否简单有不同的看法。不过在最初的时候,确实是很简单。

Rust的模块系统和JavaScript的相比有微妙的差异,当然掌握起来不困难,可在掌握之前,你经常会大呼意外

正文

如何引入文件?

最直接的回答是:你不必引入文件,在Rust中你不需要import文件。你只需要声明模块,当你声明了一个不含body的模块时,Rust会根据它的算法自动在文件系统中查找该模块,该算法和项目文件目录结构高度相关

mod关键字来声明模块,如果你写的是mod my_module;Rust会自动搜索出名为my_module.rs的文件,查询my_module的模块

不存在node中的那种相对文件路径的关系

参考:可以参考 将模块分割进不同文件

如何从其它模块引入函数

use语句可以会将其它模块的内容置于当前作用于,以便可以直接使用,这个机制对外在的依赖以及本地模块都是一样的,如:

some_module::some_function();

// 等价于
use some_module::some_function;
some_function()

可以用{}引入多个内容:

use std::io::{Read, Write};

super引用父级模块的内容:

fn work() {}

mod test {
	use super::work;
}

crate引用crate根的内容:

use crate::some_module::some_function

重要概念

crate 与 crate根

一个Rust项目就是一个cratecrate root指的是Rust编译器编译的起始源文件。在Node.js中通常是index.js,途径是通过package.json中的"main"进行配置。在Rust中,独立的可执行项目的crate rootmain.rs,代码库则的crate root则是lib.rs,这通过Cargo.toml来配置

crate root规定了Rust搜寻引入模块的根路径

module

如果你一开始就知道Rust模块与文件名或路径无关,那么后面就容易了。你可能会在文档和其它项目里看到不同的说法,不过这些说法也是假的,module仅仅是Rust内在的一个概念

一个module是一个namespace,在这里你将相似的代码归类到一块,module控制了这些代码的可见性,例如publicprivate。一个文件中可以有很多的modulemodule之间也可以嵌套使用:

fn main() {
  my_module::print(my_module::submodule::MSG);
}

mod my_module {
  pub fn print(msg: &str) {
    println!("{}", msg);
  }
  pub mod submodule {
    pub const MSG: &str = "Hello world!";
  }
}

人在划分代码结构的时候,更希望代码按照逻辑放到不同的文件中,Rust支持在本地文件系统中自动查询模块

假设我们想将my_module放到另外一个文件中,我们建立一个my_module.rs文件,并将代码拷贝过去,然后在main.rs中保留一个没有内容的mod my_module;

// src/main.rs

fn main() {
  my_module::print(my_module::submodule::MSG);
}

mod my_module;


// src/my_module.rs

pub fn print(msg: &str) {
  println!("{}", msg);
}

pub mod submodule {
  pub const MSG: &str = "Hello world!";
}

main.js声明了一个名叫my_module的模块,但是它的内容是空的,Rust就需要去寻找它的定义了

文件查询算法

  1. 从和当前模块的相同路径开始,例如你在crate root,则路径是./。如果是在one::two::three模块中,则起始就是./one/two/three
  2. 查询和导入模块同名的文件,例如mod my_module会自动查询my_module.rs文件
  3. 会在导入模块名称的文件夹中查询mod.rs文件,如my_module/mod.rs
  4. 如果有0个匹配或多个匹配都是会使问题复杂

注意:mod.rs的形式是技术遗留问题,在一些项目里可能还会看到类似的用法,但已不被推荐

这就意味着一个组织良好的项目,它的模块就是文件系统相对路径的一个镜像。看下面这段代码:


// src/mian.rs

fn main() {}

mod module_a;
mod one {
  mod two {
    mod module_b;
  }
}

mod module_amod module_b声明在同一个文件中,Rust会去哪里查询呢?

会在./module_a.rs./module_a/mod.rs中寻找module_a

会在./one/two/module_b.rs./one/two/module_b/mod.rs中寻找module_b

可见性

默认情况下所有(trait方法和枚举变体除外)都是private,不过可见性只是起到模块壁垒的作用,同一个模块内可以随意访问

定义在上层的内容默认情况下看不到更深层的内容,除非用pub来改变可见性。你可以在pub上使用修改器来调整可见性,例如:

  • pub(crate):对于同一个crate是可见的
  • pub(super):只对父模块可见

还有其他的修改器,但是不太常用,具体可以参见后面参考文章列出的相关内容

light示例的改造

之前的light项目已经变的比较大了,是时候按照文件划分进行重构了,这是一个 重构示例

参考文章

总结

mod不是import,一旦克服了这个误解,你就算明白模块系统了,下一篇文章我们会继续接上之前String未讲完的内容

更多