简介
许多实用程序都需要跨越文件系统边界:读取已有数据、处理数据,然后保存结果。这些操作可能失败,因此 Rust 的文件 API 返回 Result 值,而不是假设操作一定成功。
你将检查一份准备好的服务日志,使用 Path 表示文件位置,使用 std::fs 读取文本,生成一个简短的汇总,将汇总写入另一个文件,并使用 ? 传递 I/O 错误。
通过 Path 读取文本文件
在此步骤中,你将检查一份准备好的日志,并将其读取到 Rust 的 String 中。
项目目录是 /home/labex/project/log-summary。其中包含作为输入的 data/events.log、供后续保存报告的空 output 目录,以及作为程序源文件的 src/main.rs。进入项目目录:
cd /home/labex/project/log-summary
使用 nano 打开输入文件,了解程序将读取的内容:
nano data/events.log
该文件包含三条简短记录。不要修改文件;按 Ctrl+X 退出。现在打开 Rust 源文件:
nano src/main.rs
预先准备好的两行 use 语句让你可以使用简短名称调用 std::fs 的文件函数和 Path 类型。它们只是导入设置,不是本实验的主要主题;后续介绍模块的实验会完整讲解路径和 use 声明。返回类型 std::io::Result<()> 表示 main 要么以空成功值 () 完成,要么返回 I/O 错误。最后的 Ok(()) 表示操作成功。
将 Step 1 注释替换为:
let input_path = Path::new("data/events.log");
let contents = fs::read_to_string(input_path)?;
println!("Input: {}", input_path.display());
println!("Contents:\n{}", contents.trim_end());
Path::new 创建一个借用的文件系统路径视图,但不会打开任何文件。相对路径会从你运行 Cargo 的项目目录开始解析。fs::read_to_string 打开文件,将 UTF-8 文本读取到一个拥有所有权的 String 中,并返回一个 Result。成功时,? 会得到其中的 String;如果失败,则从 main 返回 I/O 错误。display() 用适合人阅读的形式格式化路径,trim_end() 则避免文件末尾的换行符导致输出中出现额外的空行。
按 Ctrl+O 保存,按 Enter 确认,然后按 Ctrl+X 退出。检查并运行程序:
cargo check
cargo run --quiet
Input: data/events.log
Contents:
INFO service started
WARN disk nearing limit
INFO backup complete
输出中的路径和三条记录表明程序读取了预期的文件。
统计已加载文本中的记录数
在此步骤中,你将统计内存中已有的 String 里的逻辑行数。
lines() 方法会创建一个遍历文本行的迭代器,不会修改源字符串。它的 count() 消费器会返回迭代器产生的项目数。打开源文件:
nano src/main.rs
将 Step 2 注释替换为:
let line_count = contents.lines().count();
println!("Line count: {line_count}");
保存并退出,然后运行:
cargo run --quiet
新增的最后一行应为:
Line count: 3
无论输入文件是否以换行符结尾,这段代码都能正常工作,因为 lines() 识别的是逻辑记录,而不是统计换行字节的数量。
构建稳定的汇总字符串
在此步骤中,你将统计警告记录,并将两个统计结果格式化为可以保存的文本。
字符串方法 starts_with 用于测试前缀。将它与前面已经学过的迭代器适配器结合起来,程序就可以只统计以 WARN 开头的行。打开源文件:
nano src/main.rs
将 Step 3 注释替换为:
let warning_count = contents
.lines()
.filter(|line| line.starts_with("WARN"))
.count();
let summary = format!("lines: {line_count}\nwarnings: {warning_count}\n");
println!("Summary ready:\n{}", summary.trim_end());
过滤器谓词会保留警告行,count() 则消费经过过滤的迭代器。format! 会返回一个新的 String,而不是直接打印内容;显式写入的 \n 字符会创建两行,并在末尾保留一个适合文本文件的换行符。
保存并退出,然后检查并运行:
cargo check
cargo run --quiet
输出的最后一部分应为:
Summary ready:
lines: 3
warnings: 1
现在,汇总已经作为内存中的数据准备就绪,可以写入文件系统。
写入并重新读取汇总文件
在此步骤中,你将把汇总写入准备好的输出目录,然后重新读取文件以验证结果。
fs::write(path, data) 会创建文件,或者使用提供的字节完全替换文件原有内容;它不会追加内容。与读取一样,写入也可能失败,并且会返回一个 I/O Result,因此你将使用 ? 传递错误。打开源文件:
nano src/main.rs
将 Step 4 注释替换为:
let output_path = Path::new("output/summary.txt");
fs::write(output_path, &summary)?;
let saved = fs::read_to_string(output_path)?;
println!("Saved {}:\n{}", output_path.display(), saved.trim_end());
&summary 会在 write 读取其字节期间借用汇总内容,因此 String 的所有权仍由程序持有。重新读取文件可以检查实际保存的文件,而不是直接假设写入已经成功。
保存并退出,然后检查并运行:
cargo check
cargo run --quiet
输出的最后一部分应为:
Saved output/summary.txt:
lines: 3
warnings: 1
最后检查一次生成的文件:
nano output/summary.txt
你应该能看到相同的两行汇总内容。按 Ctrl+X 退出,不要进行修改。
总结
你使用 Path 表示文件位置,使用 fs::read_to_string 读取 UTF-8 文本,使用 ? 传递 I/O 错误,汇总已加载的记录,使用 fs::write 写入结果,并重新读取保存的文件来确认其内容。


