简介
- 接上回文。
接收命令行参数
- 接收命令参数 ,比如
cargo run xxx xxx.txt
这种 - 先写一段代试试:
// 注意 std::env 必须引入进来
use std::env;
fn main() {
let args: Vec<String> = env::args().collect();
println!("======= {:?} ========", args);
}
- 如果输入 :
cargo run hello aaa bbb ccc
返回如下,表示接收参数没问题。
添加读取文件的功能
- 程序略加改进,通过
fs::read_to_string(&query_file)
读取文件内容
// 注意 std::env 必须引入进来
use std::env;
use std::fs;
fn main() {
let args: Vec<String> = env::args().collect();
println!("======= {:?} ========", args);
let query_str = &args[1];
let query_file = &args[2];
println!("Query : {}, File : {}", query_str, query_file);
// 读取文件内容
let content = fs::read_to_string(&query_file)
.expect("Something went wrong reading the query file");
println!("With text:\n{}", content);
}
重构代码以增强模块化程度和错误处理能力
- 因为main 函数又进行接收参数,又进行文件读取的双重工作,违反了单一职责原则,所以要进行拆分重构。
- 第二,query_str 和 query_file 这两个参数如果合并成结构体会让它们的用途更清晰。
- 程序修改后大致如下:
// 注意 std::env 必须引入进来
use std::{env, fs, process};
use std::error::Error;
fn main() {
// let args: Vec<String> = env::args().collect();
// println!("======= {:?} ========", args);
// let query_str = &args[1];
// let query_file = &args[2];
// println!("Query : {}, File : {}", query_str, query_file);
// // 读取文件内容
// let content = fs::read_to_string(&query_file)
// .expect("Something went wrong reading the query file");
// println!("With text:\n{}", content);
// let conf= QueryConfig::create().unwrap();
let conf=QueryConfig::create().unwrap_or_else(|err| {
// panic!("{}")
println!("Config error {:?}", err);
process::exit(1) // 一定要终否则这个conf 处理的就不完整
});
// 这种 Match 的简便写法,需要注意 if 中间是单赋值等号
if let Err(err) = run (conf) {
println!("Config error {:?}", err);
process::exit(1) // 一定要终否则这个conf 处理的就不完整
}
}
// 定义一个查询的配置结构,用来结构化配置参数
#[derive(Debug)]
struct QueryConfig {
query_file : String,
query_str : String,
}
impl QueryConfig {
// 实现 create 方法,主要提供参数验证,生成配置对象
fn create() ->Result<QueryConfig, &'static str> {
let args: Vec<String> = env::args().collect();
// 做一个验证,检查一下args 的参数个数应该恰巧 = 3 过长或者过短都有问题
if 3 != args.len() {
// panic!("参数错误,格式应该是: {query_str} {query_file}");
return Err("参数错误,格式应该是: query_str query_file")
}
Ok(QueryConfig {
// 这里要用 clone() 函数否则 args 很快就出了作用域。
query_str: args[1].clone(),
query_file: args[2].clone(),
})
}
}
// 运行函数
fn run(conf:QueryConfig) -> Result<(), Box<dyn Error>>{
//expect("Something went wrong reading the file.")
let content = fs::read_to_string(conf.query_file)?;
println!("缺少具体的run 函数内容 {}", content);
Ok(())
}
- 如上我们建立了一个 Struct 、并且给这个Struct 实现了一个 create 方法用来分离env::args 具体的变量值并存储到该结构中,另外建立了一 run 函数用来执行具体的字符查找工作,目前代码中只是将文件内容提取了出来。两个函数均采用 Resut<T,E> 作为返回值,其中run 函数不需要True返回值所以直接 T=()。
- 接下来将结构和run 函数移动到lib.rs 中。
-
lib.rs
内容大致如下(主要就是COPY过来并且开发函数和结构的pub fn):
// 注意 std::env 必须引入进来
use std::{env, fs, process};
use std::error::Error;
// 定义一个查询的配置结构,用来结构化配置参数
#[derive(Debug)]
pub struct QueryConfig {
query_file : String,
query_str : String,
}
impl QueryConfig {
// 实现 create 方法,主要提供参数验证,生成配置对象
pub fn create() ->Result<QueryConfig, &'static str> {
let args: Vec<String> = env::args().collect();
// 做一个验证,检查一下args 的参数个数应该恰巧 = 3 过长或者过短都有问题
if 3 != args.len() {
// panic!("参数错误,格式应该是: {query_str} {query_file}");
return Err("参数错误,格式应该是: query_str query_file")
}
Ok(QueryConfig {
// 这里要用 clone() 函数否则 args 很快就出了作用域。
query_str: args[1].clone(),
query_file: args[2].clone(),
})
}
}
// 运行函数
pub fn run(conf:QueryConfig) -> Result<(), Box<dyn Error>>{
//expect("Something went wrong reading the file.")
let content = fs::read_to_string(conf.query_file)?;
println!("缺少具体的run 函数内容 {}", content);
Ok(())
}
-
main.rs
内容大致如下:(一下子少了很多内容,因为移动到 lib.rs 中了)
use std::process;
use hello::QueryConfig;
fn main() {
// let conf= QueryConfig::create().unwrap();
let conf=QueryConfig::create().unwrap_or_else(|err| {
// panic!("{}")
println!("Config error {:?}", err);
process::exit(1) // 一定要终否则这个conf 处理的就不完整
});
// 这种 Match 的简便写法,需要注意 if 中间是单赋值等号
if let Err(err) = hello::run (conf) {
println!("Config error {:?}", err);
process::exit(1) // 一定要终否则这个conf 处理的就不完整
}
}
- 下一步就是完成查找功能,这里采用测试驱动方式进行这部分内容的开发,这需要在
lib.rs
中添加 #[cfg(test)] 来进行:
// 添加一个测试模块通过TDD的方式进行开发。
#[cfg(test)]
mod test {
// 引入全部类库,用于测试,这是一种简单的饮用方式,通常在测试中这样引入。
use super::*;
// 建立一个测试方法
#[test]
fn one_result() {
// 定义查询值
let query_str = "Lin";
let file_content = "\
When typed Hello world,
that means Lin Hai learn language of rust.
but rust is not friendly to him,
so Lin Hai felt very horror.
";
assert_eq!(
// 理论上查询 Lin 应该返回的 Vec
vec!["that means Lin Hai learn language of rust.", "so Lin Hai felt very horror."],
// 执行查询操作
search(query_str, file_content)
) ;
}
}
- 因为并没有编写 fn search 所以上面运行
cargo test
会报错,所以接下来开发search 的具体实现,首先报告找不到 search 所以我们现在lib.rs
中实现 search 函数:
/// 这个函数的功能用于查找 search_content 出现 query_str 的哪些行。
/// 因为传入了两个参数,这是后返回的声明周期要明确出是哪个参数的声明周期
/// 通过 <'a> 来进行声明,返回的声明周期同 search_content 的声明周期一致即可。
fn search<'a >(query_str:&str, search_content:&'a str) -> Vec<&'a str> {
vec!["lin","hai"]
}
-
然后cargo test 由于返回值我们是随便写的所以可以看到具体的报错,已经从找不到search 函数变成了左右值不匹配了,所以接下来实现它即可。
// 稍稍改进一下
fn search<'a >(query_str:&str, search_content:&'a str) -> Vec<&'a str> {
// vec!["lin","hai"]
let mut resutl_vec : Vec<&str> = Vec::new();
// 按行遍历字符串
for line in search_content.lines() {
if line.contains(query_str) {
resutl_vec.push(line);
}
}
resutl_vec
}
- 执行
cargo test
测试通过了。
- 剩下的就是应用search 函数到 run 函数中即可,修改 run 函数:
// 运行函数
pub fn run(conf:QueryConfig) -> Result<(), Box<dyn Error>>{
//expect("Something went wrong reading the file.")
let content = fs::read_to_string(conf.query_file)?;
// println!("缺少具体的run 函数内容 {}", content);
let result_vec = search(&conf.query_str, &content);
println!("Query string : {}, Match line : {:?}", conf.query_str, result_vec);
Ok(())
}
- 执行
cargo run lin hello.txt
返回结果,结果正常(需要自行创建 hello.txt 的文件内容。)
更近一步,为不区分大小写的search 函数编写一个失败的测试
- 在 lib.rs 的测试中补一个测试:
#[test]
fn two_result() {
// 定义查询值
let query_str = "lIn";
let file_content = "\
When typed Hello world,
that means Lin Hai learn language of rust.
but rust is not friendly to him,
so Lin Hai felt very horror.
";
assert_eq!(
// 理论上查询 Lin 应该返回的 Vec
vec!["that means Lin Hai learn language of rust.", "so Lin Hai felt very horror."],
// 执行查询操作
search(query_str, file_content)
) ;
}
- 上面的测试显然会运行失败,接下来我们修改 search 函数让其支持大小写不敏感的匹配。
fn search<'a >(query_str:&str, search_content:&'a str) -> Vec<&'a str> {
// 将查询字符串转成小写:
let query_str = query_str.to_lowercase();
// vec!["lin","hai"]
let mut resutl_vec : Vec<&str> = Vec::new();
// 按行遍历字符串
for line in search_content.lines() {
if line.to_lowercase().contains(&query_str) {
resutl_vec.push(line);
}
}
resutl_vec
}
-
cargo test 测试通过,接下来就可以 cargo run 试试了
将错误提示信息打印到标准错误而不是标准输出。
- 目前通过 println! 宏打印全部是输出到终端上也就是 stdout 我们接下来要按照规范将错误信息输出到 stderr 上面。
- 其实很简单要输出到标准错误上面(stderr)只需要使用eprintln!() 即可。
结束
- 感谢阅读。