Rust 基础知识19 - 实例自己写一个文件内容行的匹配函数

简介

  • 接上回文。

接收命令行参数

  • 接收命令参数 ,比如 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 返回如下,表示接收参数没问题。
    image.png

添加读取文件的功能

  • 程序略加改进,通过 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 函数变成了左右值不匹配了,所以接下来实现它即可。


    image.png
// 稍稍改进一下
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 测试通过了。
    image.png
  • 剩下的就是应用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 的文件内容。)
    image.png

更近一步,为不区分大小写的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 试试了


    image.png

将错误提示信息打印到标准错误而不是标准输出。

  • 目前通过 println! 宏打印全部是输出到终端上也就是 stdout 我们接下来要按照规范将错误信息输出到 stderr 上面。
  • 其实很简单要输出到标准错误上面(stderr)只需要使用eprintln!() 即可。

结束

  • 感谢阅读。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,293评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,604评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,958评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,729评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,719评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,630评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,000评论 3 397
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,665评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,909评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,646评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,726评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,400评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,986评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,959评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,996评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,481评论 2 342

推荐阅读更多精彩内容