7. Package、Crate 和 Module
7.1 Package、Crate 和 Module 的定义
Rust 的代码组织包括:
- 哪些细节可以暴露,哪些细节私有
- 作用域内哪些名称有效
模块系统:
- 包(Package):Cargo 的一个功能,可以让我们构建、测试、共享 crate
- 单元包(Crate):一个模块树,可产生一个二进制文件或库
- 模块(Module):控制代码组织、作用域和路径
- 路径(Path):用于引用模块树中的项
Crate 的类型为 binary 和 library。src/main.rs
是二进制 crate 的入口,src/lib.rs
是库 crate 的入口。Crate Root 是编译器从 crate 开始的文件。
我们也可以在 src/bin
目录下创建多个二进制 crate。
一个 Package 包含一个 Cargo.toml
文件,描述如何构建 crate。只能包含一个库 crate,可以包含任意多个二进制 crate。但至少包含一个 crate。
Crate 可以将相关功能组合到一个作用域内,便于在项目间进行共享,而且能防止命名冲突。例如 rand
crate 包含了随机数生成器,访问它的功能需要使用它的名字并 use rand
。
定义 Module 来控制作用域和私有性。在一个 crate 中,模块将代码进行分组。控制项目(Item)的可见性,可以使用 pub
关键字。
使用 mod
关键字创建模块。模块树形结构,模块可以包含在其他模块中。模块可以嵌套。
mod front_of_house {
mod hosting {
fn add_to_waitlist() {}
}
}
src/main.rs
和 src/lib.rs
文件是 crate root。这两个文件形成了名为 crate
的模块,位于模块树的根部。
7.2 路径
可以使用 crate
关键字来从根模块开始访问其他模块,也可以使用 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();
}
模块不仅能组织代码,还能定义私有边界(privacy boundary)。如果想把函数或 struct
设为私有,可以方到模块内部。Rust 中所有条目默认是私有的,使用 pub
关键字来使其公有。
父模块无法访问子模块中的私有内容,但子模块可以访问父模块中的内容。
7.3 父级模块路径
使用 super
关键字来访问父级模块的内容。
fn serve_order() {}
mod back_of_house {
fn fix_incorrect_order() {
cook_order();
super::serve_order();
}
fn cook_order() {}
}
使用 pub struct
来定义公有结构体,但结构体的字段默认是私有的。可以使用 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"),
}
}
}
}
枚举的成员默认是公有的,不需要在其前面加 pub
关键字。
7.4 使用 use 关键字
使用 use
关键字将路径引入作用域,可以使用相对路径或绝对路径。
一般对于 struct
、enum
和函数,直接使用 use
关键字将其引入作用域,而不是引入其父级。
use std::collections::HashMap;
fn main() {
let mut map = HashMap::new();
map.insert(1, 2);
}
同名模块必须指定到父级,或者使用 as
关键字重命名。
use std::fmt::Result;
use std::io::Result as IoResult;
fn function1() -> Result {
// --snip--
}
fn function2() -> IoResult<()> {
// --snip--
}
使用 use
引入的名称对于外部是私有的。如果想要将名称变为公有,可以使用 pub use
。
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();
}
7.5 使用外部包
要使用外部包,可以在 Cargo.toml
文件中添加依赖项。Cargo 会在 crates.io 上查找依赖项。
下载好之后使用 use
关键字引入。
可以使用嵌套路径来引入外部包的子模块,语法为 use xxx::{yyy, zzz}
。
use std::{cmp::Ordering, io};
如果要引入当前名称,使用 self
关键字。
// use std::fmt::Result;
// use std::fmt;
use std::fmt::{self, Result};
我们可以使用 *
通配符来引入所有公有项。
use std::collections::*;
7.6 模块的文件系统
如果定义模块时,直接使用分号而不是代码块,则 Rust 会从同名文件中加载模块。
mod front_of_house;
模块层级与文件系统层级一致。例如 src/front_of_house.rs
和 src/front_of_house/hosting.rs
。