Module
在编写较为复杂的项目时,合理地对代码进行组织与管理很重要,rust 提供了一系列的功能来帮助我们管理代码,包括决定哪些细节是暴露的、哪些细节是私有的,以及不同的作用域内存在哪些名称。
通过定义模块来控制作用域及私有性
在 src 目录下创建 lib.rs 文件,使用 mod 关键字加大括号定义一个模块,可以多层嵌套:
// src/lib.rs
mod front_of_house {
// 模块中的内容默认都是隐藏的,需要使用pub将其公开
pub mod hosting {
pub fn add_to_waiting() {}
fn seat_to_table() {}
}
mod serving {
fn take_order() {}
fn serve_order() {
fn take_payment() {}
}
}
fn eat_at_restaurant() {
// 引用模块内容
// 绝对路径使用crate开头,因为front_of_house是根模块,
// 可以理解为绝对路径: /front_of_house
crate::front_of_house::hosting::add_to_wait_list();
// 使用相对路径,因为front_of_house和
// eat_at_restaurant在同一级,可以理解为:./front_of_house
front_of_house::hosting::add_to_wait_list()
}
复制代码
super 关键字
每一个模块是单独的作用域,而且作用域并不会自动向上查找,查找当前模块的上级作用域,可以使用 super:
// back_of_house模块的作用域外部
fn serve_order() {}
mod back_of_house {
fn cook_order() {}
fn fix_incorrent_order() {
// 当前模块作用域中可以直接使用
cook_order();
// 获取父级模块的内容,可是使用super关键字开头,可以理解为:../
super::serve_order()
}
}
复制代码
公开的结构体
当我们在结构体定义前使用 pub 时,结构体本身就成为了公共结构体,但它的字段依旧保持了私有状态。我们可以逐一决定是否将某个字段公开:
mod back_of_house {
pub struct Breakfast {
// 只公开toast字段
pub toast: String,
// 这个字段仍然是私有的
seasonal_fruit: String,
}
impl Breakfast {
pub fn summer(toast: &str) -> Breakfast {
Breakfast {
toast: String::from(toast),
seasonal_fruit: String::from("peaches"),
}
}
}
}
pub fn eat_at_restaurant() {
let mut meal = back_of_house::Breakfast::summer("Rye");
// 可以访问更改toast属性
meal.toast = String::from("Wheat");
println!("I'd like {} toast please", meal.toast);
// I'd like Wheat toast please
// 无法编译通过,因为seasonal_fruit不是公开属性
meal.seasonal_fruit = String::from("blueberries");
}
eat_at_restaurant();
复制代码
公开的枚举
当我们将一个枚举声明为公共的时,它所有的变体都自动变为了公共状态。我们仅需要在 enum 关键字前放置 pub:
mod back_of_house {
// 对枚举添加pub,会将所有的枚举值公开
pub enum Appetizer {
Soup,
Salad,
}
}
pub fn eat_at_restaurant() {
let order1 = back_of_house::Appetizer::Soup;
let order2 = back_of_house::Appetizer::Salad;
}
复制代码
使用 use 关键字引用模块
use 可以引用当前模块中的内容,支持绝对路径和相对路径:
mod front_of_house {
pub mod hosting {
pub fn add_to_wait_list() {}
}
}
// 绝对路径方式
use crate::front_of_house::hosting; // 方式1
use crate::front_of_house::hosting::add_to_wait_list; // 方式2
// 或者相对路径
use self::front_of_house2::hosting; // 方式1
use self::front_of_house2::hosting::add_to_wait_list; // 方式2
fn eat_at_restaurant() {
hosting::add_to_wait_list() // 方式1使用
add_to_wait_list() // 方式2使用
}
复制代码
使用 as 关键字对引用模块重命名
当我们引用相同名称的内容会引起冲突,这是需要使用 as 关键字对其中一项进行重命名:
use std::fmt::Result;
use std::io::Result as IoResult;
复制代码
重新导出(reexporting)
我们对用户暴露的内容通常需要暴露在一个出口中,这样方便用户导入,
我们需要先将其他的引用导入到同一个模块中,然后重新导出:
pub use crate::front_of_house2::hosting;
pub use std::fmt::Result;
复制代码
外部模块
当我们使用第三方模块时,需要先在 cargo.toml 中添加依赖,例如使用一个能够获取随机数的包:
[dependencies]
rand = "0.5.5"
// 运行 cargo build
复制代码
引入 rand:
use rand::Rng;
fn main() {
let secret_number = rand::thread_rng().gen_range(1, 101);
}
复制代码
引入标准库中的模块,标准库虽然不用在 toml 中声明,也不用下载,但是也需要显式引入:
use std::collections::HashMap;
复制代码
嵌套批量导入
use std::{cmp::Ordering, io};
// 等同于
use std::cmp::Ordering;
use std::io;
复制代码
使用 self 表示引用导入路径的自身:
use std::io::{self, Write};
// 僧同于
use std::io;
use std::io::Write;
复制代码
使用*运算符导入所有
// 引入collections下面所有的内容
use std::collections::*
// 直接使用引入的内容
BTreeMap::new();
复制代码
将模块拆分到不同的文件
上边的所有例子都是在同一个文件中,我们可以把不同功能的代码拆分到不同的文件中维护,有两种方式可以实现,例如我们要在 lib.rs 中使用如下结构的模块:
// src/lib.rs
mod a; // 引入a模块
use a::structs::{ A1, A2 }; // 使用a模块中的结构体中的A1,A2结构体
复制代码
方式一
使用模块同名文件夹结构来组织,这种组织方式将一个模块的同名文件夹作为模块的内容,模块本身作为入口文件,首先创建 a.rs 文件作为 a 模块的入口文件:
创建 a 模块的同名文件夹,用于放置 a 模块的内容:
在文件夹 a 中创建结构体模块:
创建 A1 和 A2 结构体:
// src/a/structs.rs
pub struct A1 {}
pub struct A2 {}
复制代码
最后在 a 模块的入口文件中引入 structs 模块并向外导出:
// src/a.rs
pub mod structs;
复制代码
最后结构如下:
src
├── a
│ └── structs.rs
├── a.rs
├── lib.rs
复制代码
方式二
使用模块入口放在模块文件夹中的方式来组织,这种方式将文件夹作为模块名称,在文件夹中使用 mod.rs 作为模块入口,首先创建模块文件夹:
在文件夹 a 中创建结构体模块:
创建 A1 和 A2 结构体:
// src/a/structs.rs
pub struct A1 {}
pub struct A2 {}
复制代码
本次不再创建 src/a.rs 文件,而是在文件夹 a 中放置入口文件 mod,名称是固定的,就好像我们在 JS 中一般使用 index.js 如出一辙:
在入口文件中引入并导出 a 模块的内容:
// src/a/mod.rs
pub mod structs;
复制代码
这种方式的目录接否如下:
src
├── a
│ ├── mod.rs
│ └── structs.rs
├── lib.rs
复制代码
本章节只介绍了模块的基本用法和两种目录结构的使用方式,更详细的内容可以自行去翻阅原书。
评论