用 rustlings 系统掌握 Rust 模块系统:pub 可见性、use 路径导入与 pub use 重导出
本篇围绕 rustlings 仓库中 exercises/10_modules/ 模块章节(对应官方文档 exercises/10_modules/README.md)展开,介绍 Rust 模块系统(module system)的三个核心能力:默认私有与 pub 可见性控制、use/as 将模块路径引入作用域并重命名、以及通过 pub use 向外部暴露与内部结构不同的接口。你将跟随 modules1~modules3 三道练习题的完整代码,掌握如何在真实工程中组织代码可见性与命名空间。
模块章节定位与练习构成
exercises/10_modules/README.md 是 rustlings 课程中"模块"章节的导读,其核心目标是给出 Rust 模块系统的入门指引。在 rustlings 的练习体系中,该章节由三道源码文件组成,各自聚焦一个知识点:
| 练习文件 | 考察重点 | 官方提示(hint) |
|---|---|---|
| modules1.rs | mod 与可见性:模块内函数默认私有,需用 pub 对外暴露 |
"Everything is private in Rust by default. But there's a keyword we can use to make something public!"(Rust 中一切默认私有,但有一个关键字可以把它变为公开) |
| modules2.rs | use ... as 重命名 + pub use 重导出 |
提示要补齐两条 use 语句,并找到"两个常量都缺少的一个关键字" |
| modules3.rs | 用嵌套路径一行引入标准库模块 | 提示 UNIX_EPOCH 与 SystemTime 声明在 std::time 模块中,可用嵌套路径一行引入 |
三道练习的 hint 文本集中维护在 rustlings-macros/info.toml 的 [[exercises]] 配置段中,其中 modules1~modules3 均标记为 test = false,意味着 rustlings 只要求练习编译通过(modules3 额外要求运行时不 panic),而不依赖单元测试断言。
modules1:模块边界与 pub 可见性
练习代码与报错
modules1.rs 构造了一个典型的"模块内部实现"场景——香肠工厂(sausage_factory 模块):
// TODO: Fix the compiler error about calling a private function.
mod sausage_factory {
// Don't let anybody outside of this module see this!
fn get_secret_recipe() -> String {
String::from("Ginger")
}
fn make_sausage() {
get_secret_recipe();
println!("sausage!");
}
}
fn main() {
sausage_factory::make_sausage();
}
这段代码无法通过编译:main 中调用 sausage_factory::make_sausage() 时,make_sausage 没有 pub 修饰,属于私有项,模块外部(main 所在的根作用域)不可访问。这正是 Rust 的可见性规则——一切默认私有,且可见性按模块边界逐层生效:只有当前模块及其子模块能看到当前模块中的私有项。
修复方式与官方解法
对照 solutions/10_modules/modules1.rs,官方解法只改了一处:
mod sausage_factory {
fn get_secret_recipe() -> String {
String::from("Ginger")
}
// Added `pub` before `fn` to make the function accessible outside the module.
pub fn make_sausage() {
get_secret_recipe();
println!("sausage!");
}
}
fn main() {
sausage_factory::make_sausage();
}
get_secret_recipe 保持私有,make_sausage 加上 pub。这个细节值得注意:修复的最小改动是只暴露必要的 API(make_sausage),而不必把"秘方"函数也公开。这体现了 Rust 模块设计的一个惯用做法——用模块边界封装内部实现,对外仅暴露最小公开面;调用方只能经由 make_sausage 间接触达 get_secret_recipe。
modules2:use/as 引入路径与 pub use 重导出
练习代码
modules2.rs 要求模块对外呈现一套"与内部结构不同的接口"。内部结构是两个子模块 fruits 和 veggies,各自定义公开常量:
mod delicious_snacks {
// TODO: Add the following two `use` statements after fixing them.
// use self::fruits::PEAR as ???;
// use self::veggies::CUCUMBER as ???;
mod fruits {
pub const PEAR: &str = "Pear";
pub const APPLE: &str = "Apple";
}
mod veggies {
pub const CUCUMBER: &str = "Cucumber";
pub const CARROT: &str = "Carrot";
}
}
fn main() {
println!(
"favorite snacks: {} and {}",
delicious_snacks::fruit,
delicious_snacks::veggie,
);
}
注意 main 中期望的路径是 delicious_snacks::fruit 和 delicious_snacks::veggie——小写、无子模块前缀,与内部 fruits::PEAR、veggies::CUCUMBER 的结构完全不同。因此需要两件事:
- 用
use ... as把常量路径引入delicious_snacks作用域并重命名; - 让外部能沿
delicious_snacks::fruit这个路径找到它们——即use本身还要公开。
修复方式:pub use 重导出
对照 solutions/10_modules/modules2.rs:
mod delicious_snacks {
// Added `pub` and used the expected alias after `as`.
pub use self::fruits::PEAR as fruit;
pub use self::veggies::CUCUMBER as veggie;
mod fruits {
pub const PEAR: &str = "Pear";
pub const APPLE: &str = "Apple";
}
mod veggies {
pub const CUCUMBER: &str = "Cucumber";
pub const CARROT: &str = "Carrot";
}
}
几个要点:
self::前缀:在模块内部以self指代当前模块本身,self::fruits::PEAR即"本模块下 fruits 子模块的 PEAR"。as重命名:use self::fruits::PEAR as fruit;创建别名fruit,使main中delicious_snacks::fruit的写法成立。pub use重导出:这是官方 hint 中所说的"两个常量都缺少的一个关键字"。若只写use(不加pub),别名仅在当前模块内部可见,main依然无法访问;加上pub后,delicious_snacks模块就对外重导出了fruit/veggie两个名字,而内部真实结构(fruits、veggies子模块及常量)仍然保持私有。这正是"模块作为接口边界"的体现——外部使用者只看到delicious_snacks::fruit,看不到其内部组织。
modules3:一行引入标准库模块(嵌套路径)
modules3.rs 的场景是计算当前时间距 UNIX 纪元的秒数,代码中直接使用了 SystemTime 与 UNIX_EPOCH,但尚未引入作用域:
// TODO: Bring `SystemTime` and `UNIX_EPOCH` from the `std::time` module into
// your scope. Bonus style points if you can do it with one line!
// use ???;
fn main() {
match SystemTime::now().duration_since(UNIX_EPOCH) {
Ok(n) => println!("1970-01-01 00:00:00 UTC was {} seconds ago!", n.as_secs()),
Err(_) => panic!("SystemTime before UNIX EPOCH!"),
}
}
官方解法 solutions/10_modules/modules3.rs 用一条嵌套路径语句同时引入两个名字,拿到题目所说的"Bonus style points":
use std::time::{SystemTime, UNIX_EPOCH};
这里 use 的"路径"不限于自己项目的模块树,同样适用于标准库(std::time)。大括号语法 use 路径::{A, B}; 允许在一条语句中导入同一模块下的多个项,比写两行 use std::time::SystemTime; use std::time::UNIX_EPOCH; 更简洁。test = false 的校验方式下,该练习的通过标准是:编译通过,且运行 main 正常打印(若时钟早于 UNIX 纪元会触发 panic,正常系统下不会)。
rustlings 如何组织与校验这些练习
从仓库结构看,模块章节的练习文件、解法文件与校验配置是分离维护的:
- 练习源码位于 exercises/10_modules/,配套解法位于 solutions/10_modules/,一一对应;
- dev/Cargo.toml 将每个练习注册为 workspace 成员,并同时引入练习版与解法版两个 target,例如:
{ name = "modules1", path = "../exercises/10_modules/modules1.rs" },
{ name = "modules1_sol", path = "../solutions/10_modules/modules1.rs" },
{ name = "modules2", path = "../exercises/10_modules/modules2.rs" },
{ name = "modules2_sol", path = "../solutions/10_modules/modules2.rs" },
{ name = "modules3", path = "../exercises/10_modules/modules3.rs" },
{ name = "modules3_sol", path = "../solutions/10_modules/modules3.rs" },
这说明 rustlings 的校验流程是把每个 .rs 练习作为独立编译单元进行编译(_sol 版本则用于验证解法可编译通过);
- 每个练习的元数据(目录、是否含测试、hint 文本)记录在 rustlings-macros/info.toml 中,模块三道题均为
test = false,即纯编译校验,与 tests/test_exercises/info.toml 中带测试用例(如test_failure)的练习形成对照; - 练习文件顶部的
// TODO:注释即为需要修改的位置,官方 hint 会给出方向而不直接给答案(如 modules1 的 hint 只提示"有一个关键字可以让项变公开")。
学习者通常通过 cargo run(rustlings CLI)或 cargo dev 驱动开发循环来完成本章节:阅读报错 → 按 hint 方向修改 → 编译/运行通过即过关。
小结:模块三要素速查
| 语法 | 作用 | 本章节出处 |
|---|---|---|
mod name { ... } |
声明模块,划定作用域边界,内部项默认私有 | modules1.rs |
pub |
使项(函数/常量/模块)对模块外部可见 | modules1 解法、modules2 解法 |
use 路径 as 别名; |
引入模块路径并在新作用域重命名 | modules2 解法 |
pub use ...; |
重导出名字,对外暴露与内部结构不同的接口 | modules2 解法 |
use a::b::{X, Y}; |
嵌套路径,一行引入多个标准库项 | modules3 解法 |
完成本章后,建议继续回顾 Rust 官方书籍 The Book 第 7 章("Managing Growing Projects with Packages, Crates, and Modules")中"用 use 关键字把路径引入作用域"一节的 pub use 重导出小节,以补全本章练习涉及的完整知识背景。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0622
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00