Rust 有许多功能可以让你管理代码的组织,包括哪些内容可以被公开,哪些内容作为私有部分,以及程序每个作用域中的名字。这些功能,有时被统称为 “模块系统(the module system)”,包括:
- 包 ( Packages ):Cargo 的一个功能,它允许你构建、测试和分享 crate。
- Crates :一个模块的树形结构,它形成了库或二进制项目。
- 模块 ( Modules )和 use :允许你控制作用域和路径的私有性。
- 路径 ( path ):一个命名例如结构体、函数或模块等项的方式
包和Crates
crate 是 Rust 在编译时最小的代码单位。如果你用 rustc 而不是 cargo 来编译一个文件(第一章我们这么做过),编译器还是会将那个文件认作一个 crate。crate 可以包含模块,模块可以定义在其他文件,然后和 crate 一起编译。
crate 有两种形式:二进制项和库。 二进制项 可以被编译为可执行程序,比如一个命令行程序或者一个服务器。
库 并没有 main 函数,它们也不会编译为可执行程序,它们提供一些诸如函数之类的东西,使其他项目也能使用这些东西。
包 ( package )是提供一系列功能的一个或者多个 crate。一个包会包含一个 Cargo.toml 文件,阐述如何去构建这些 crate。Cargo 就是一个包含构建你代码的二进制项的包。Cargo 也包含这些二进制项所依赖的库。其他项目也能用 Cargo 库来实现与 Cargo 命令行程序一样的逻辑。
包中可以包含至多一个库 crate(library crate)。包中可以包含任意多个二进制 crate(binary crate),但是必须至少包含一个 crate(无论是库的还是二进制的)。
模块
- 从 crate 根节点开始 : 当编译一个 crate, 编译器首先在 crate 根文件(通常,对于一个库 crate 而言是 src/lib.rs ,对于一个二进制 crate 而言是 src/main.rs )中寻找需要被编译的代码。
- 声明模块: 在 crate 根文件中,你可以声明一个新模块;比如,你用 mod garden 声明了一个叫做 garden
- 的模块。编译器会在下列路径中寻找模块代码:
- 内联,在大括号中,当 mod garden 后方不是一个分号而是一个大括号
- 在文件 src/garden.rs
- 在文件 src/garden/mod.rs
- 声明子模块: 在除了 crate 根节点以外的其他文件中,你可以定义子模块。比如,你可能在src/garden.rs中定义了 mod vegetables; 。编译器会在以父模块命名的目录中寻找子模块代码:
- 内联,在大括号中,当 mod vegetables 后方不是一个分号而是一个大括号
- 在文件 src/garden/vegetables.rs
- 在文件 src/garden/vegetables/mod.rs
- 模块中的代码路径 : 一旦一个模块是你 crate 的一部分,你可以在隐私规则允许的前提下,从同一个 crate 内的任意地方,通过代码路径引用该模块的代码。举例而言,一个 garden vegetables 模块下的 Asparagus 类型可以在 crate::garden::vegetables::Asparagus 被找到。
- 私有 vs 公用 : 一个模块里的代码默认对其父模块私有。为了使一个模块公用,应当在声明时使用 pub mod 替代 mod 。为了使一个公用模块内部的成员公用,应当在声明前使用 pub 。
- use 关键字 : 在一个作用域内, use 关键字创建了一个成员的快捷方式,用来减少长路径的重复。在任何可以引用 crate::garden::vegetables::Asparagus 的作用域,你可以通过 use crate::garden::vegetables::Asparagus; 创建一个快捷方式,然后你就可以在作用域中只写 Asparagus 来使用该类型。
声明模块
使用 mod xxx 的方式来声明模块。
mod front_of_house {
mod hosting {
fn add_to_waitlist() {}
fn seat_at_table() {}
}
mod serving {
fn take_order() {}
fn serve_order() {}
fn take_payment() {}
}
}
分别声明了front_of_house模块以及其子模块。
如果上面的模块采用Tree形式, 将如下结构
crate
└── front_of_house
├── hosting
│ ├── add_to_waitlist
│ └── seat_at_table
└── serving
├── take_order
├── serve_order
└── take_payment
引用模块
引用模块即如何再模块树中找到对应的位置,Rust中使用路径的方式。
路径方式的两种形式:
- 绝对路径 ( 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();
}
绝对路径, 即从根开始依次找到对应的位置。
相对路径即相对当前模块依次找到对应的位置。
其中, 相对路径可以使用self\super的方式来定位, 类似于 . 和 .. ,即当前模组或上层模组
共有、私有
当一个模组中要被其他模组方式,则要使用 pub 的形式共开。
例如上面的代码, eat_at_restaurant的函数中, 要访问add_to_waitlist的函数则需要pub hosting模块以及pub add_to_waitlist的函数。
注意:Struct 结构体,如果在模块中, 其字段需要公开后访问。
enum ,公开后则其所有的成员都是公开的。
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 fn eat_at_restaurant() {
// 在夏天订购一个黑麦土司作为早餐
let mut meal = back_of_house::Breakfast::summer("Rye");
// 改变主意更换想要面包的类型
meal.toast = String::from("Wheat");
println!("I'd like {} toast please", meal.toast);
// 如果取消下一行的注释代码不能编译;
// 不允许查看或修改早餐附带的季节水果
// meal.seasonal_fruit = String::from("blueberries");
}
mod back_of_house {
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 关键字将路径引入作用域。简化这个过程。
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的方式引入到作用域。
mod front_of_house {
pub mod hosting {
pub fn add_to_waitlist() {}
}
}
use crate::front_of_house::hosting::add_to_waitlist;
pub fn eat_at_restaurant() {
add_to_waitlist();
}
- 可以使用as关键字提供新的名称
use std::fmt::Result;
use std::io::Result as IoResult;
fn function1() -> Result {
// --snip--
Ok(())
}
fn function2() -> IoResult<()> {
// --snip--
Ok(())
}
- 使用pub use重导出名称, 重导出 技术将一个路径导出,允许别人把它导入他们自己的作用域中。
- Use 可以使用嵌套方式消除大量的use行
// --snip--
use std::cmp::Ordering;
use std::io;
// --snip--
// 变为如下
use std::{cmp::Ordering, io};
use std::io;
use std::io::Write;
//->
use std::io::{self, Write};
- Use 可以通过glob运算符引入所有的公有定义引入到作用域
use std::collections::*;
使用外部包
- Cargo.toml 中加入引入的包
rand = "0.8.5"
- use 引用的包
use std::io;
use rand::Rng;
fn main() {
println!("Guess the number!");
let secret_number = rand::thread_rng().gen_range(1..=100);
println!("The secret number is: {secret_number}");
println!("Please input your guess.");
let mut guess = String::new();
io::stdin()
.read_line(&mut guess)
.expect("Failed to read line");
println!("You guessed: {guess}");
}