7.1 模块系统:管辖权划分


7.1 模块系统:管辖权划分

本节摘要:模块把 crate 内部划分为命名空间与可见性边界,默认私有、pub 才对外。本节讲 mod 声明、模块树、路径引用与 pub 的各种粒度。读完你能把单文件项目拆成清晰的多模块结构。

默认私有:封闭审理原则

mod archive { pub struct Case { pub no: u32, // 字段也要逐个公开 sealed: bool, // 私有:外界不可见、不可构造 } impl Case { pub fn new(no: u32) -> Self { Case { no, sealed: false } } pub fn seal(&mut self) { self.sealed = true; } } } let mut c = archive::Case::new(173); // 用路径访问 c.seal();

默认私有的收益:内部实现可以随意重构,只要 pub 面不变。Case 有私有字段意味着外界无法绕过构造函数制造非法状态——第 4.2 节"让非法状态不可表示"的工程落地。

模块树与路径

mod 可以嵌套、可以搬到独立文件(目录与同名文件两种组织法都行)。路径有绝对(crate 开头)与相对(self、super 开头)两种;use 把常用路径引入作用域,as 可重命名避免撞名。pub 的粒度还有细化版:pub(crate) 对整个 crate 可见——库内部共享但不进公共 API 的标准写法。

图:模块树与可见性

图:模块树与可见性

要点回顾

  • mod 是命名空间加边界,默认封闭审理,pub 才开放;
  • 路径从 crate 根算起,use 引入缩写;
  • pub(crate) 是内部共享的中间档;
  • 私有字段强制走构造函数,非法状态的又一防线。

模块树的物理布局

同一棵模块树可以对应多种文件摆法,编译器只认"声明与目录约定",认清楚就能自由选择。

// src/main.rs —— 根模块 mod evidence; // 对应 src/evidence.rs 或 src/evidence/mod.rs fn main() { let id = evidence::new_id(); println!("编号 {}", id); archive::file(&id.to_string()); } mod archive { // 内联模块:小工具就近安家 pub fn file(name: &str) { println!("归档 {}", name); } }
// src/evidence.rs pub(crate) fn new_id() -> u32 { 173 } // 仅本 crate 可见 pub fn public_api() {} // 完全公开 pub(super) fn helper() {} // 只对父模块公开(此处父是根)

可见性修饰是三个档次而非两档,工程价值巨大:pub(crate) 是内部协作的默认档,pub(super) 用于只向直属上级负责的工具,全 pub 只留给真正的对外承诺。审阅一个 crate 的 API 面积,数一数真正的 pub 就够了。

use 的四种姿势与重导出

use std::collections::HashMap; // 常规引入 use std::fmt::{self, Display}; // 组合引入 use std::io::Result as IoResult; // 改名避开冲突 pub use crate::evidence::new_id as issue_id; // 重导出:私路径公开化

重导出 pub use 是库的门面手法:内部按层分目录,对外用根模块统一陈列,调用方不必感知内部结构。标准库的 prelude 就是这一手法集大成者。

路径解析的方言差异

edition 2018 起外部路径必须从 crate 名或 use 引入起步,本地路径以 crate::self::super:: 开头。use evidence::X 在旧判例集(2015)里指本地模块,在新集里是外部包,迁移老代码时这是最高频的错位点,报错是 E0432 "unresolved import",修法是补 crate:: 前缀。

划界的三个实操判例

判例一:工具函数从私有到公开的迁移路径。先 pub(crate) 服务内部,出现真实外部需求才升 pub——先内后外的顺序保证 API 面积不虚胖。判例二:深层嵌套的治理。三层以上的 mod 嵌套通常意味着职责不清,用 re-export 把常用项抬到浅层,比让调用方写长路径更体面。判例三:测试的归属。能测私有细节的单元测试放 #[cfg(test)] 同文件;跨模块行为的验证放 tests/ 目录,从外部视角审阅——两席分工好,"测试逼着改可见性"的坏味道自然消失。

// src/lib.rs —— 一个典型的浅门面 mod internal { pub(crate) fn checksum(data: &[u8]) -> u16 { data.iter().fold(0, |a, b| a + *b as u16) } } pub use internal::checksum as public_checksum; // 门面重导出:内部深,外部浅

判例三的补充:#[cfg(test)] mod tests 写在被测文件底部是社区主流,隔文件放 tests.rs 也合法,团队统一即可,真正不可妥协的是"集成测试只走公开 API"这条边界。

模块重构的完整走查

把一个膨胀的 main.rs 拆成模块树,标准动作六步:新建 src/lib.rs 承接可复用逻辑;按职责切出一级模块(如 evidence、archive);main.rs 只留参数解析与调度;跨模块引用统一走 use 路径并清理未用项;为每个模块补一句 doc 注释说明管辖权;跑全量测试确认零回归。走查中最容易翻车的是第四步——pub 档位拿捏:先全 pub(crate) 让编译通过,再逐个降级到最小可见性,编译器会用 E0624 一类报错指出哪里降过头。这个"先通后严"的次序比一次到位省时得多。

// 拆分后的 main.rs 应当短到可以整屏看完 use demo::evidence; use demo::archive; fn main() { let args: Vec<String> = std::env::args().skip(1).collect(); let id = evidence::parse_id(args.first()).expect("需要案号参数"); archive::file(&format!("卷宗{}", id)); }

作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U