Rust学习——TRPL-Part8

Managing Growing Projects with Packages, Crates, and Modules

目前所写的程序都是 in one module in one file。随着项目的增长,可以通过将代码分解成多个module 和 多个file 来组织代码。本章将会涵盖所有这些概念。对于一个由一系列相互关联的包组合而成的超大型项目,Cargo 提供了 “工作空间” 这一功能,我们将在第十四章的 “Cargo Workspaces” 对此进行讲解。

除了对功能进行分组以外,封装实现细节可以使你更高级地重用代码:你实现了一个操作后,其他的代码可以通过该代码的公共接口来进行调用,而不需要知道它是如何实现的。你在编写代码时可以定义哪些部分是其他代码可以使用的公共部分,以及哪些部分是你有权更改实现细节的私有部分。这是另一种减少你在脑海中记住项目内容数量的方法。

Rust 有许多功能可以让你管理代码的组织,包括哪些内容可以被公开,哪些内容作为私有部分,以及程序每个作用域中的名字。这些功能。这有时被称为 “模块系统(the module system)”,包括:

  • (Packages): Cargo 的一个功能,它允许你构建、测试和分享 crate。
  • Crates :一个模块的树形结构,它形成了库或二进制项目。
  • 模块(Modules)和 use: 允许你控制作用域和路径的私有性。
  • 路径(path):一个命名例如结构体、函数或模块等项的方式

Pacakges and Crates

模块系统的第一个部分,包(packages)与 crates。crate 是一个二进制项或者库。包(package) 是提供一系列功能的一个或者多个 crate。

包中所包含的内容由几条规则来确立。

  • 一个包中至多只能包含一个库 crate(library crate)
  • 包中可以包含任意多个二进制 crate(binary crate)
  • 包中至少包含一个 crate,无论是库的还是二进制的。

Cargo 遵循的一些个规定 src/main.rs 就是一个与包同名的二进制crate的crate根,同样Cargo如果指导包目录中包含 src/lib.rs, 则包有与其同名的库crate,且 src/lib.rs 是crate根。 如果一个包同时含有 src/main.rssrc/lib.rs, 则它有两个 crate: 一个库和一个二进制项,且名字都与包相同。通过将文件放在 src/bin 目录下,一个包可以拥有多个二进制crate:每个 src/bin 下的文件都会被编译成一个独立的二进制crate。

Defining Modules to Control Scope and Privacy

模块让我们可以将一个crate中的代码分组,以提高可读性与重用性。模块还可以能否被外部代码使用的private 和 public。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
mod front_of_house {
mod hosting {
fn add_to_waitlist() {}

fn seat_at_table() {}
}

mod serving {
fn take_order() {}

fn server_order() {}

fn take_payment() {}
}
}

我们定义一个模块,是以 mod 关键字为起始,然后指定模块的名字(本例中叫做 front_of_house),并且用花括号包围模块的主体。在模块内,我们还可以定义其他的模块,就像本例中的 hostingserving 模块。模块还可以保存一些定义的其他项,比如结构体、枚举、常量、特性、或者函数。(回忆 Coq 里面的Module划分)

注意:lib.rsmain.rs叫做crate根。之所以这样叫它们是因为这两个文件的内容都分别在 crate 模块结构的根组成了一个名为 crate 的模块,该结构被称为 模块树(module tree)。(整个模块树都根植于crate的隐式模块下,类似于文件目录中的\根目录)。

通过使用模块,我们可以将相关的定义分组到一起,并指出他们为什么相关。

Defining Modules to Control Scope and Privacy

路径用于引用模块树中的项

路径的两种形式:

  • 绝对路径absolute path)从crate 根开始,以crate名或者字面值crate开头
  • 相对路径relative path)从当前模块开始,以selfsuper或者当前模块的标识符开头

双冒号::分隔。

模块不仅对于你组织代码很有用。他们还定义了 Rust 的 私有性边界(privacy boundary):这条界线不允许外部代码了解、调用和依赖被封装的实现细节。所以,如果你希望创建一个私有函数或结构体,你可以将其放入模块。

创建公有的结构体和枚举

结构体设为公有pub,则结构体公有,而字段仍为私有。

枚举设为公有,所有成员都变为公有。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#![allow(unused)]
pub fn main(){
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;
}
}

习惯决定的。 还有一种pub的场景我们还没有涉及到,那就是我们最后要讲的模块功能:use关键字。

Bringing Paths into Scope with the use Keyword

使用use关键字将路径一次性引入作用域

1
2
3
4
5
6
7
8
9
10
11
12
13
14
mod front_of_house {
pub mod hosting {
pub fn add_to_waitlist() {}
}
}

use front_of_house::hosting;

pub fn eat_at_restaurant() {
hosting::add_to_waitlist();
hosting::add_to_waitlist();
hosting::add_to_waitlist();
}
fn main() {}

创建惯用的use路径

  • 通过use关键字将函数的父模块引入作用域(习惯用法)
  • 通过use关键字将函数直接引入作用域

前者可以清除地表达函数的定义,并不是在本地定义的而是在制定的模块路径,同时最小化路径的重复,但是后者就会模糊add_to_waitlist是在哪里被定义的。

注意:Rust不允许使用use语句将两个具有相同名称的项带入作用域,解决方法是引入父模块(此时路径需要带有父模块名),或者使用as关键字为引入的同名模块提供新的名称。

使用 pub use 重导出名称

仅使用use关键字导入作用域时,新作用域名称私有的,换言之,调用本模块的无法使用这些新引入作用域。pub use,技术称为重导出,re-exporting,因为这样做将项引入作用域并同时使其可供其他代码引入。

1
pub use crate::front_of_house::hosting;

外部可以引入后也可使用新路径hosting::add_to_waitlist

使用外部包


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!