12 一个I/O项目:构建命令行程序
本章我们将会构建一个与文件和命令行输入/输出交互的命令行工具来练习已经学过的Rust技能
Rust的运行速度、安全性、单二进制文件输出和跨平台支持使其成为创建命令行程序的绝佳选择,所以我们将创建一个我们自己的经典命令行工具grep(globally research a regular expression and print)
Grep最简单的使用场景是在特定文件中搜索指定字符串
在这个过程中,我们的命令行工具会用到终端功能,读取环境变量来使得用户可以配置工具,打印到标准错误控制流而不是标准输出。这样用户可以选择将成功输出重定向到文件中同时也能够在屏幕上显示错误信息
并且我们在本章会涉及代码组织、vector和字符串、错误处理、trait和生命周期以及测试,另外闭包、迭代器我们也也会了解一点点
12.3 重构以改进模块化与错误处理
我们先来看一下这个函数有哪些问题
第一:main函数在这里既解析了参数又执行了文件打开和访问,承担的内容过多
第二:query和filename是程序中的配置变量,而像contents则用来执行程序逻辑,如果程序大的话,这种类似的变量会变得越来越多,它们也会变得越来越难以追踪
第三:我们在这个程序中只使用了expect来处理错误,这远远不能否,给出的信息太模糊
第四:这个程序多个地方都可能会出现错误,我们希望能够集中在一处处理这些错误
二进制项目的关注分离
main函数负责多个任务的组织问题在许多二进制项目中非常常见,所以我们关注项目变大时的代码分离,步骤如下:
将程序拆分放入main.rs和lib.rs,并将程序的逻辑放入lib.rs中
当命令行解析逻辑比较小时,可以保留在main.rs中
当命令行变得复杂时,也同样将其从main.rs提取到lib.rs中
经过上述拆分,main函数的责任应被限制为:
使用参数值调用命令行解析逻辑
设置配置
调用lib.rs中的run函数
如果run函数返回错误,处理这个错误
我们随后接下来按照这种逻辑重构代码
提取参数解析器
fn main() {
let args:Vec<String> = env::args().collect();
let (query,filename) = parse_config(&args);
println!("Searching for {}",query);
println!("In file {}",filename);
let contents = fs::read_to_string(filename)
.expect("Something went wrong reading the file");
println!("With text:\n{}", contents)
}
fn parse_config(args:&[String])-> (&str,&str) {
let query = &a