简介
随着 Rust 程序不断增长,把所有类型和函数都放在一个文件中会让职责变得难以区分。你已经在 CLI 实验中接触过库和二进制文件,也在测试实验中接触过测试模块。本实验将这些准备好的示例整合到一个模型中:软件包(package)是 Cargo 构建的项目,crate 是编译单元,模块(module)用于组织 crate 中的名称。
你将组织一个小型 pantry-report 软件包。文件和大部分实现代码都已准备好,因此每个步骤只关注一个边界:从库 crate 中共享内容、声明文件模块,以及将一个小型公共 API 导入二进制 crate。
在 Crate 之间共享函数
在本步骤中,你将识别软件包包含的两个 crate,并让二进制 crate 可以使用一个库函数。
进入准备好的 Cargo 软件包目录:
cd /home/labex/project/pantry-report
Cargo.toml 描述了一个名为 pantry-report 的软件包。Cargo 会将 src/lib.rs 识别为库 crate 的根文件,将 src/main.rs 识别为二进制 crate 的根文件。它们属于同一个软件包,但会作为独立的 crate 分别编译。
查看这两个较短的 crate 根文件。sed -n 命令只打印指定的行范围,不会修改文件:
sed -n '1,120p' src/lib.rs
sed -n '1,120p' src/main.rs
二进制 crate 通过库 crate 的路径导入 report_title。在 Rust 源代码中,包含连字符的软件包名称会转换为包含下划线的 crate 名称,因此 pantry-report 会变成 pantry_report。
准备好的返回类型 &'static str 表示一个借用的字符串字面量,该字符串在整个程序运行期间都有效。'static 标记是生命周期注解。显式生命周期规则不在本入门课程的范围内;本实验不要求你理解或编写生命周期注解。
除非使用 pub 标记,否则条目默认只对其所在模块私有。打开库根文件:
nano src/lib.rs
只将函数声明从:
fn report_title() -> &'static str {
改为:
pub fn report_title() -> &'static str {
pub 会将该函数加入库 crate 的公共接口。按 Ctrl+O 保存,按 Enter 确认,然后按 Ctrl+X 退出。
使用 cargo check 检查两个 crate 的类型,但不生成最终的可运行构建文件:
cargo check
然后运行二进制 crate:
cargo run --quiet
输出应为:
Pantry Report
这说明二进制 crate 已经能够跨越 crate 边界,调用公开的库函数。
声明文件模块
在本步骤中,你将把准备好的 inventory.rs 文件连接到库 crate 的模块树。
Rust 源文件仅仅存在并不会自动参与编译。crate 根文件必须声明对应模块。对于名为 inventory 的声明,Rust 会查找 src/inventory.rs,并将其中的条目放在 inventory::... 路径下。
查看准备好的模块文件。和之前一样,sed -n '1,200p' 使用 -n 禁止自动输出,并通过 1,200p 只打印指定的行范围:
sed -n '1,200p' src/inventory.rs
#[derive(Debug)] 这一行要求 Rust 为 Item 生成标准的调试格式化支持。当前可见的程序并不依赖此功能,因此将它视为已准备好的元数据,而不是新的要求。Item 类型、它的字段以及 describe 都是公开的。辅助函数 availability 没有 pub,因此仍然只在该模块内部可用。describe 可以调用这个私有辅助函数,而模块外部的调用者只能获得公开的结果。
打开库根文件:
nano src/lib.rs
将 // MODULE_DECLARATION 替换为:
pub mod inventory;
开头的 pub 会通过库 crate 暴露该模块。mod inventory; 部分则将这个文件连接到模块树中。保存并退出 Nano,然后检查软件包:
cargo check
Finished `dev` profile ...
检查成功表示 Rust 已找到 src/inventory.rs,并将其作为 pantry_report::inventory 编译。
导入并使用公共 API
在本步骤中,你将使用 use 声明把模块条目引入二进制 crate,并打印一条 pantry 记录。
如果不使用导入,完整路径分别是 pantry_report::inventory::Item 和 pantry_report::inventory::describe。use 声明会在当前作用域中创建较短的名称,不会移动或复制这些条目。
打开二进制源文件:
nano src/main.rs
将 // INVENTORY_IMPORT 替换为以下分组导入:
use pantry_report::inventory::{describe, Item};
大括号将共享相同路径前缀的两个条目分组。接下来,将 // INVENTORY_REPORT 替换为:
let lentils = Item {
name: String::from("lentils"),
quantity: 4,
};
println!("{}", describe(&lentils));
由于类型和两个字段都是公开的,二进制 crate 可以构造 Item。它会将该条目的借用传递给 describe;这个公开函数会在自己的模块内部调用私有的 availability。
保存并退出 Nano,然后运行软件包:
cargo run --quiet
Pantry Report
lentils: 4 jars (stocked)
这两行输出表明,crate 级别的共享和模块级别的组织都已通过一个小型公共 API 正常工作。
总结
你操作了一个包含库 crate 和二进制 crate 的 Cargo 软件包,将库条目设为公开,声明了文件模块,使用 crate 和模块路径,通过 use 缩短了这些路径,并将实现辅助函数隐藏在一个小型公共 API 后面。


