type
Post
date
Nov 5, 2025
slug
Rust/notebook/12
summary
学习下 Rust 圣经,记录笔记
status
Published
tags
Rust
category
技术茶点
icon
password
😀
概括前面学到的知识探索更多的标准库功能
核心内容是:
  1. 构建一个命令行工具:将使用 Rust 来构建一个自己的经典命令行搜索工具 grep(用于在文件中搜索指定字符串)。
  1. 练习 I/O 交互:学习如何与文件和命令行输入/输出进行交互。
  1. 高级命令行功能:涵盖读取命令行参数读取文件处理环境变量将错误信息输出到标准错误流stderr)等。
这个项目将结合并练习之前章节中学到的代码组织、Vector 和字符串、错误处理、Trait 和生命周期、以及测试等概念。

12.一个 I/O 项目:构建命令行程序

12.1.接受命令行参数

概念
含义
关键点
获取命令行参数
使用 Rust 标准库中的 std::env::args() 来获取传给程序的参数。 (Rust 文档)
args() 返回一个迭代器;通常用 .collect() 转成 Vec<String>。 (Rust 文档)
参数顺序与程序名
第一个参数 args[0] 是程序自身的路径或名称,后续参数是用户传入的。 (Rust 文档)
所以如果你期望两个参数,通常是 args[1]args[2] 分别代表你定义的“查询字符串”“文件路径”等。
示例:读取指定两个参数
例如定义程序接受两个参数:queryfile_path。 (Rust 文档)
示例代码中:rust<br>let args: Vec<String> = env::args().collect();<br>let query = &args[1];<br>let file_path = &args[2]; (Rust 文档)
无足够参数的风险
如果用户没有提供预期的参数,直接访问 args[1]args[2] 会导致运行时 panic。 (Rust 文档)
建议后续章节中加入参数数量检查 + 返回错误或提示用户。
Unicode/操作系统差异
env::args() 假定参数为有效 UTF-8 字符串;若需处理无效 Unicode,可用 env::args_os() 返回 OsString。 (Rust 文档)
在跨平台或非 UTF-8 环境时需注意。
 

12.2.读取文件

概念
含义
关键点
读取文件内容
使用 Rust 标准库中 std::fs::read_to_stringstd::fs::File + read_to_string 等方法读取整个文件到字符串。 (Rust 文档)
let contents = fs::read_to_string(file_path).expect("…"); — 最简写法。 (Rust 文档)
处理错误
文件读取返回 Result<String, std::io::Error>,需要处理或断言。 (Rust 文档)
临时代码里可用 expect(),但生产代码应更完整处理。
使用 File::open + read_to_string 方法
更显式地打开文件句柄、读出内容,再合并为字符串。 (Massachusetts Institute of Technology)
示例:rust<br>let mut f = File::open(filename).expect("file not found");<br>let mut contents = String::new();<br>f.read_to_string(&mut contents).expect("error reading file");
模块引入
需要 use std::fs;use std::fs::File; use std::io::prelude::*; 等。 (Massachusetts Institute of Technology)
方便之后函数调用,无需全路径。
项目结构与样例输入
建议在项目根目录放置测试文件,比如 poem.txt,用于验证读取逻辑。 (Rustの日本語ドキュメント/Japanese Docs for Rust)
在命令行中传入该文件路径为参数,测试读取功能。
改进空间
虽然功能可工作,但代码将多个职责(参数读取、文件读取、内容处理)放在 main,不利于扩展。建议将读取逻辑拆成函数、改为返回 Result 等。 (Rust 文档)
提前考虑结构可提高项目可维护性。
 

12.3.重构以改进模块化与错误处理

概念
含义
关键点
模块化结构(Separation of Concerns)
将项目中不同职责(如:解析命令行、配置构建、读取文件、搜索逻辑、错误处理)拆分成不同模块/函数。 (Rust 文档)
例如,将逻辑从 main 提取到 lib.rsrun 函数中,使 main 保持简洁。 (Rust 文档)
配置结构体(Config struct)
用结构体封装程序需要的配置变量(如搜索词、文件路径、是否忽略大小写),比直接使用多个独立变量更清晰。 (Rust 文档)
这样代码可读性更强,后续扩展也更方便。
改进错误处理
避免大量使用 expect()panic!(),改用返回 Result<T, E> 的方式,将错误传回 main 统一处理。 (Rust 文档)
例如 run(config) -> Result<(), Box<dyn Error>>,在 main 中处理 Err(e) 并退出。
统一错误处理入口
将错误处理逻辑集中在 main 中,使程序结构清晰,易于维护。 (Massachusetts Institute of Technology)
这样未来维护或变更时,只需在一个地方调整错误处理方式。
提炼函数 & 测试友好
把功能细分为小函数/模块,如 Config::buildrun 函数,方便编写单元测试。 (Rust 文档)
测试逻辑不在 main 中,而在 lib.rs 内部函数,从而提升测试覆盖与可维护性。
 

12.4.采用测试驱动开发完善库的功能

概念
含义
关键点
测试驱动开发 (TDD)
先编写一个失败的测试 → 编写最少量代码让其通过 → 重构代码 → 重复。 (Massachusetts Institute of Technology)
这种流程有助于确保库的核心功能被覆盖并持续保持正确。
将逻辑从 main 提取至 lib
为了方便测试,将命令行解析、错误处理放在 main.rs,核心逻辑(如 search 函数)放在 src/lib.rs,便于单元测试。 (Rust 文档)
这样测试不依赖命令行调用,而是直接调用函数,更快捷、稳定。
为新功能编写测试
比如新增 search(query, contents) -> Vec<&str> 函数,先写测试 ASSERT 出期待结果。 (Rust 文档)
示例中:搜索 “duct” 在文本中只返回包含该词的那一行。
代码实现使测试通过
按步骤:遍历每行 → 检查是否包含查询 → 将匹配行推入返回列表 → 返回。 (Rust 文档)
一步步从测试失败到成功,再重构。
使用生命周期参数
由于函数返回对传入 contents 的切片引用,需要指定生命周期 <'a>,以让编译器知道返回引用不会悬垂。 (Massachusetts Institute of Technology)
函数签名示例: fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str>
将被测试功能整合进程序运行逻辑
完成功能后,从 run 函数调用 search 并输出结果,使程序整体起作用。 (Rust 文档)
用于 CLI 程序如 minigrep query filename 的最终运行效果。
 

12.5.处理环境变量

概念
含义
关键点
环境变量(Environment Variables)
程序运行时,可由操作系统/用户在 shell 中设置的键-值对,用于控制程序行为。 (doc.rust-lang.org)
在本例中,程序通过环境变量 IGNORE_CASE 来决定是否进行不区分大小写的搜索。 (doc.rust-lang.org)
读取环境变量
使用标准库模块 std::env::var("VAR_NAME") 来查询某个环境变量。 (rust-book.cs.brown.edu)
返回 Result<String, std::env::VarError>,表示变量是否已设置。
将环境变量与逻辑关联
程序读取变量是否 已设置 (不用关心具体值),然后将其作为配置项决定程序行为。 (doc.rust-lang.org)
例如:let ignore_case = env::var("IGNORE_CASE").is_ok(); 表示如果 IGNORE_CASE 已设置,则 ignore_case = true。 (rust-book.cs.brown.edu)
环境变量 vs 命令行参数
环境变量适用于“用户不想每次输入但希望一次设置多次使用”的场景;命令行参数适合“每次运行可变”场景。 (doc.rust-lang.org)
在设计 CLI 程序时,可混用参数和环境变量,并决定优先级。 (rust-book.cs.brown.edu)
跨平台注意
不同操作系统或 shell 设置环境变量的语法可能不同。 (doc.rust-lang.org)
如在 Linux/macOS: IGNORE_CASE=1 cargo run -- query filename;在 Windows PowerShell 可能 $Env:IGNORE_CASE=1; cargo run -- query filename。 (Rustの日本語ドキュメント/Japanese Docs for Rust)
 

12.6.将错误信息输出到标准错误而不是标准输出

概念
含义
关键点
stdout vs stderr
在命令行工具中,标准输出(stdout)用于正常结果输出;标准错误(stderr)用于输出错误或诊断信息。 (doc.rust-lang.org)
如果只重定向 stdout(用 >),但错误信息也通过 stdout 输出,则会被输到文件,而不是显示在屏幕上。
为何使用 stderr 输出错误
这样用户可以把正常结果重定向到文件,但仍然在屏幕看到错误信息;符合 Unix/CLI 工具惯例。 (doc.rust-lang.org)
常见场景:cargo run > output.txt 若错误通过 stderr 输出,屏幕会显示错误而 output.txt 只含正常结果。
如何在 Rust 中写入 stderr
使用 eprintln!() 宏,它会把格式化信息写到 stderr。 (Massachusetts Institute of Technology)
例如:eprintln!("Problem parsing arguments: {err}");
更手动方式写入 stderr
若需更控制或旧版 Rust,可用 writeln!(&mut std::io::stderr(), …) 写入 stderr。 (itfanr.gitbooks.io)
例如:let mut stderr = std::io::stderr(); writeln!(&mut stderr, "Error: {}", err).expect("Could not write to stderr");
在项目中的典型应用时机
当程序解析命令行参数失败、读取文件失败、运行逻辑错误时,用 stderr 输出错误并退出;正常运行结果用 stdout 输出。 (doc.rust-lang.org)
main() 中:若 Config::build 返回错误,则 eprintln!()process::exit(1);否则继续运行并把结果通过 println!() 输出。
树莓派构建AI健康小屋《Rust 程序设计语言》(11/22)
Loading...