Rust模块系统
Rust官方的模块系统教程太啰嗦了,以下内容根据官方教程精简。
Crate
crate 是 Rust 在编译时最小的代码单位。一个crate是一个源文件。
crate 有两种形式:二进制 crate 和库 crate。
- 二进制 crate(Binary crates)必须有一个名为
main的函数,可以被编译为可执行程序,比如命令行程序或者服务端。 - 库 crate(Library crates)并没有
main函数,它们也不会编译为可执行程序。相反它们定义了可供多个项目复用的功能模块,这与其他编程语言中 “library” 概念一致。
crate root 是一个源文件,Rust 编译器以它为起始点编译代码。
Cargo 遵循一个约定:
src/main.rs是一个与包同名的二进制 crate 的 crate root。src/lib.rs是一个与包同名的库 crate 的 crate root。
包(Packages)
包(package)是提供一系列功能的一个或者多个 crate 的捆绑。一个包会包含一个 Cargo.toml 文件。
Cargo创建的项目就是一个包。
一个包(package)可以包含多个二进制 crate 项和一个可选的库 crate。
通过将文件放在 src/bin 目录下,一个包可以拥有多个二进制 crate:每个 src/bin 下的文件都会被编译成一个独立的二进制 crate。
模块(Modules)
模块让我们可以将一个 crate 中的代码进行分组,以提高可读性与重用性。因为一个模块中的代码默认是私有的,所以还可以利用模块控制项的私有性(Privacy) 。私有项是不可为外部使用的内在详细实现。我们也可以将模块和它其中的项标记为公开的,这样,外部代码就可以使用并依赖于它们。
声明模块: 在 crate root 文件中,使用 mod 关键字声明一个新模块。例如:
mod garden;
内联定义模块:将声明模块后的分号改为大括号,其中提供模块代码。例如:
mod garden {
// 模块中可定义函数、结构体、枚举、常量、trait等。
}
在单独的文件中定义模块:当在crate 根文件中声明了一个模块,例如mod garden;,在src/garden.rs文件中定义模块代码。当在garden.rs中声明了子模块vegetables,则在src/garden/vegetables.rs文件中定义子模块代码。
隐式根模块:src/main.rs 和 src/lib.rs 这两个文件的内容在 crate 模块结构的根组成了一个名为 crate 的模块。
模块树(module tree):模块结构是树状结构。
公开模块成员
模块里的成员默认对父模块私有。为了公开模块中的成员,应当在声明前使用pub。
子模块
在模块中声明模块,即子模块。例如在src/garden.rs文件中:
mod vegetables;
为了公开子模块,声明时使用 pub mod 替代 mod。
pub mod vegetables;
结构体
模块中,结构体的字段也是默认私有的,需要使用pub公开字段:
mod back_of_house {
pub struct Breakfast {
pub toast: String,
seasonal_fruit: String,
}
impl Breakfast {
pub fn summer(toast: &str) -> Breakfast {
Breakfast {
toast: String::from(toast),
seasonal_fruit: String::from("peaches"),
}
}
}
}
以上代码,因为 back_of_house::Breakfast 具有私有字段,所以这个结构体需要提供一个公共的关联函数来构造 Breakfast 的实例。
枚举
如果将枚举设为公有,则它的所有变体都将变为公有。
mod back_of_house {
pub enum Appetizer {
Soup,
Salad,
}
}
调用模块成员
为了调用模块中的成员,要完整指定成员的路径,多层路径之间用双冒号(::)分割。路径有两种形式:
- 绝对路径(absolute path)是以 crate 根(root)开头的完整路径;对于外部 crate 的代码,是以 crate 名开头的绝对路径,对于当前 crate 的代码,则以字面值
crate开头。 - 相对路径(relative path)从当前模块开始,以
self、super或当前模块中的某个标识符开头。
例如:
mod front_of_house {
pub mod hosting {
pub fn add_to_waitlist() {}
}
}
pub fn eat_at_restaurant() {
// 绝对路径
crate::front_of_house::hosting::add_to_waitlist();
// 相对路径
front_of_house::hosting::add_to_waitlist();
}
可以通过在路径的开头使用 super ,从父模块开始构建相对路径,调用父模块成员:
fn deliver_order() {}
mod back_of_house {
fn fix_incorrect_order() {
cook_order();
super::deliver_order();
}
fn cook_order() {}
}
用use 关键字引入模块成员
使用use 关键字引入模块成员,以便减少调用成员编写过长的路径。
例如引入子模块:
mod front_of_house {
pub mod hosting {
pub fn add_to_waitlist() {}
}
}
use crate::front_of_house::hosting;
pub fn eat_at_restaurant() {
hosting::add_to_waitlist();
}
引入结构体:
use std::collections::HashMap;
fn main() {
let mut map = HashMap::new();
map.insert(1, 2);
}
通常只引入结构体、枚举和子模块,而不直接引入函数。在调用函数时指定其模块,这样可以清晰地表明函数不是在本地定义的,同时使完整路径的重复度最小化。
use引入的成员只在当前作用域起作用,在子模块中是另一个作用域,不起作用:
mod front_of_house {
pub mod hosting {
pub fn add_to_waitlist() {}
}
}
use crate::front_of_house::hosting;
mod customer {
pub fn eat_at_restaurant() {
hosting::add_to_waitlist(); // 此处编译报错
}
}
使用 as 关键字指定别名
不能引入两个同名类型,除非给其中一个类型指定别名。
use std::fmt::Result;
use std::io::Result as IoResult;
fn function1() -> Result {
// --snip--
}
fn function2() -> IoResult<()> {
// --snip--
}
使用 pub use 重导出名称
使用 use 关键字,将某个名称导入当前作用域后,该名称对此作用域之外还是私有的。若要让作用域之外的代码能够像在当前作用域中一样使用该名称,可以将 pub 与 use 组合使用。这种技术被称为重导出(re-exporting),因为在把某个项目导入当前作用域的同时,也将其暴露给其他作用域。
例如在restaurant包中:
mod front_of_house {
pub mod hosting {
pub fn add_to_waitlist() {}
}
}
pub use crate::front_of_house::hosting;
pub fn eat_at_restaurant() {
hosting::add_to_waitlist();
}
在这个修改之前,外部代码需要使用路径 restaurant::front_of_house::hosting::add_to_waitlist() 来调用 add_to_waitlist 函数,并且还需要将 front_of_house 模块标记为 pub。现在这个 pub use 从根模块重导出了 hosting 模块,外部代码现在可以使用路径 restaurant::hosting::add_to_waitlist。
通过 glob 运算符引入成员
如果希望将一个路径下所有公有项引入作用域,可以指定路径后跟 * glob 运算符:
use std::collections::*;
使用 glob 运算符时请多加小心!glob 会使得我们难以推导作用域中有什么名称和它们是在何处定义的。
glob 运算符经常用于测试模块 tests 中,这时会将所有内容引入作用域。
本文来自博客园,作者:星墨,转载请注明原文链接:https://www.cnblogs.com/yada/p/19691159

浙公网安备 33010602011771号