12|模块与库:落地 TextLab 的计算核心
目标:把统计逻辑放进可复用的库,让二进制入口只负责调用与展示。
前面分别验证了输入借用、结构化结果和迭代统计,现在可以把它们放在同一个实现中。TextLab 从本课开始拥有真正的计算核心。拆分依据是职责:统计函数只知道文本,不知道文件来自哪里;入口知道怎么展示结果,不重复统计规则。
一个 Cargo package 可以同时包含库 crate 和二进制 crate。src/lib.rs 是库的根,src/main.rs 是二进制的根。模块用 mod 声明,条目默认私有;pub 允许从外部访问。use 只是把路径引入作用域,不会把文件复制进来。pub use 可以把内部模块的选定接口重新导出,调用者不必知道内部文件布局。
建立三份完整文件
沿用第 1 课的 Cargo.toml 和 examples/。创建 src/stats.rs:
pub struct Report {
pub lines: usize,
pub words: usize,
pub bytes: usize,
pub chars: usize,
}
impl Report {
pub fn summary(&self) -> String {
format!(
"lines={} words={} bytes={} chars={}",
self.lines, self.words, self.bytes, self.chars
)
}
}
pub fn analyze(text: &str) -> Report {
Report {
lines: text.lines().count(),
words: text.split_whitespace().count(),
bytes: text.len(),
chars: text.chars().count(),
}
}
本课程把报告作为简单的数据记录,因此开放字段,方便调用者组合和比较。若将来字段之间存在必须维护的不变量,可以改成私有字段与受控方法。
创建 src/lib.rs:
mod stats;
pub use stats::{Report, analyze};
mod stats; 声明模块并按约定加载同目录的 stats.rs。模块本身仍是私有的,但两个公共条目通过库根重新导出。完整替换 src/main.rs:
use textlab::analyze;
fn main() {
let report = analyze("你好 Rust\n");
println!("{}", report.summary());
}
运行前先预测:二进制需要自己再次声明 mod stats 吗?
cargo check
cargo run --quiet
预期输出:
lines=1 words=2 bytes=12 chars=8
二进制通过包中库的名字 textlab 使用接口,不需要重复声明模块。输入被借用四次,分别计算已明确的统计单位;返回值只保存数字,可以在输入销毁后继续存在。这里进行了多次遍历,对小型文本工具清晰易查;还没有根据测量证据要求改成一次遍历。
用另一个调用者检验接口
- 创建
examples/use_library.rs,写入下面完整代码。 - 运行
cargo run --quiet --example use_library。 - 预期为
lines=1 words=2 bytes=10 chars=10。示例与二进制共享同一份实现。
use textlab::analyze;
fn main() {
println!("{}", analyze("rust cargo").summary());
}
独立练习:给库增加 pub fn count_words(text: &str) -> usize,并通过库根导出。让 examples/use_library.rs 能调用它,断言 "a b\n" 为 2、空串为 0。
参考修改与验收
在 src/stats.rs 末尾加入这个局部扩展:
pub fn count_words(text: &str) -> usize {
analyze(text).words
}
把 src/lib.rs 的导出行改为 pub use stats::{Report, analyze, count_words};。把 examples/use_library.rs 完整替换为:
use textlab::count_words;
fn main() {
assert_eq!(count_words("a b\n"), 2);
assert_eq!(count_words(""), 0);
println!("public API checks passed");
}
运行该 example,预期 public API checks passed。这为教学复用了 analyze,也会计算其他字段;如果调用只关心词数,直接 .split_whitespace().count() 能避免无用遍历。练习完成后恢复三个主文件与引导示例的状态,后续不依赖这个扩展。
如果出现 unresolved import,检查函数是否 pub,以及库根是否导出。如果找不到 stats 文件,检查它是否位于 src/stats.rs,而不是练习目录根。如果主入口重复声明模块导致你修改了一份、测试另一份,按本课恢复成“库声明模块,二进制通过库名调用”的结构。
检查点:项目现在有 Cargo.toml、Cargo.lock、src/main.rs、src/lib.rs、src/stats.rs 以及所有 examples/*.rs。后续保留这些文件,在明确标出的地方增量修改。