首页 >热点 > > 正文

一起来学rust|简单的mingrep

博客园 2023-05-14 21:19:43

2023-5-14,完成于新冠病隔离期间。是我学习rust起步,意在通过这个小的项目,学习一些基本的rust的思想,熟悉rust的基本语法知识。


(资料图片)

学习到的内容总结

Rust知名项目grep替代

https://github.com/BurntSushi/ripgrep

实现的功能

ps:查看cargo参数的方法,cargo --list

大体的形式

传入,--告诉后面的参数给程序使用而不是cargo

cargo run -- searchstring example-filename.txt

程序接受参数并且处理

//in main.rs//引入环境包,可以处理传入的参数use std::env;fn main() {    //将内容转换为数组集合    let args: Vec = env::args().collect();    //使用 dbg!宏,读取数组内容    dbg!(args);}

尝试一下

env::args` 读取到的参数中第一个就是程序的可执行路径名

$ cargo run   Compiling minigrep v0.1.0 (file:///projects/minigrep)    Finished dev [unoptimized + debuginfo] target(s) in 0.61s     Running `target/debug/minigrep`[src/main.rs:5] args = [    "target/debug/minigrep",]
$ cargo run   Compiling minigrep v0.1.0 (file:///projects/minigrep)    Finished dev [unoptimized + debuginfo] target(s) in 0.61s     Running `target/debug/minigrep`[src/main.rs:5] args = [    "target/debug/minigrep",]

不可信输入

这里,选择直接了当,让用户知道自己错了,而不是人为去处理这些不可信输入。

这里有一个思考的点:就是不可信输入的处理想法

所有的用户输入都不可信!不可信!不可信!重要的话说三遍,我们的命令行程序也是,用户会输入什么你根本就不知道,例如他输入了一个非 Unicode 字符,你能阻止吗?显然不能,但是这种输入会直接让我们的程序崩溃!原因是当传入的命令行参数包含非 Unicode 字符时, std::env::args 会直接崩溃,如果有这种特殊需求,建议大家使用 std::env::args_os,该方法产生的数组将包含 OsString 类型,而不是之前的 String 类型,前者对于非 Unicode 字符会有更好的处理。至于为啥我们不用,两个理由,你信哪个:1. 用户爱输入啥输入啥,反正崩溃了,他就知道自己错了 2. args_os 会引入额外的跨平台复杂性

项目创建

cargo new minigrep     Created binary (application) `minigrep` project$ cd minigrep

实现基本的功能

存储读取到的参数

给予清晰合理的变量名是一项基本功,咱总不能到处都是 args[1] 、args[2] 这样的糟糕代码吧。

//in main.rs//引入环境包,可以处理传入的参数use std::env;fn main() {    //将内容转换为数组集合    let args: Vec = env::args().collect();    //待搜查字符串    //存储文件路径    let query = &args[1];    let file_path = &args[2];    println!("Searching jfor {}",query);    println!("In file {}",file_path);    //使用 dbg!宏,读取数组内容    //dbg!(args);}

文件读取

在根目录创建内容poem.txt

I"m nobody! Who are you?我啥也不是,你呢?Are you nobody, too?牛逼如你也是无名之辈吗?Then there"s a pair of us - don"t tell!那我们就是天生一对,嘘!别说话!They"d banish us, you know.你知道,我们不属于这里。How dreary to be somebody!因为这里属于没劲的大人物!How public, like a frog他们就像青蛙一样呱噪,To tell your name the livelong day成天将自己的大名To an admiring bog!传遍整个无聊的沼泽!

代码逻辑

use std::fs;    //省略    //读取文件内容,记得异常处理    let context = fs::read_to_string(file_path)        .expect("Should have been able to read the file!");    //捕获变量输出    print!("With text:\n {context}"); 

不足点总结

完美,虽然代码还有很多瑕疵,例如所有内容都在 main 函数,这个不符合软件工程,没有错误处理,功能不完善等。不过没关系,万事开头难,好歹我们成功迈开了第一步。但凡稍微没那么糟糕的程序,都应该具有代码模块化和错误处理,不然连玩具都谈不上。梳理我们的代码和目标后,可以整理出大致四个改进点:

改进程序(增加模块化和错误处理)

分离main函数思路(启动与逻辑)

如何处理庞大的 main 函数,Rust 社区给出了统一的指导方案:

按照这个方案,将我们的代码重新梳理后,可以得出 main 函数应该包含的功能:

这个方案有一个很优雅的名字: 关注点分离(Separation of Concerns)。简而言之,main.rs 负责启动程序,lib.rs 负责逻辑代码的运行。从测试的角度而言,这种分离也非常合理: lib.rs 中的主体逻辑代码可以得到简单且充分的测试,至于 main.rs ?确实没办法针对其编写额外的测试代码,但是它的代码也很少啊,很容易就能保证它的正确性。

分离main中命令解析

//in main.rsfn main() {    //将内容转换为数组集合    let args: Vec = env::args().collect();    //2.0,命令解析    let (query, file_path) = parse_config(&args);    //省略}// in main.rs//解析函数fn parse_config(args: &[String]) -> (&str,&str) {   let query = &args[1];   let file_path = &args[2];   (query,file_path)}

聚合配置变量

配置变量并不适合分散的到处都是,因此使用一个结构体来统一存放是非常好的选择,这样修改后,后续的使用以及未来的代码维护都将更加简单明了

//in main.rs//引入环境包,可以处理传入的参数use std::env;use std::fs;fn main() {    //将内容转换为数组集合    let args: Vec = env::args().collect();    //待搜查字符串    //存储文件路径    //let query = &args[1];    //let file_path = &args[2];    //2.0    let config = parse_config(&args);    println!("Searching for {}",config.query);    println!("In file {}",config.file_path);    //读取文件内容,记得异常处理    let context = fs::read_to_string(config.file_path)        .expect("Should have been able to read the file!");    //捕获变量    print!("With text:\n{context}");    //使用 dbg!宏,读取数组内容    //dbg!(args);}// in main.rs//聚合变量struct Config {    query:String,    file_path:String,}// 命令解析//这里选择clone, 防止所有权带来的问题fn parse_config(args: &[String]) -> Config {   let query = args[1].clone();   let file_path = args[2].clone();   Config {query,file_path}}

clone 的得与失在上面的代码中,除了使用 clone,还有其它办法来达成同样的目的,但 clone无疑是最简单的方法:直接完整的复制目标数据,无需被所有权、借用等问题所困扰,但是它也有其缺点,那就是有一定的性能损耗。因此是否使用 clone更多是一种性能上的权衡,对于上面的使用而言,由于是配置的初始化,因此整个程序只需要执行一次,性能损耗几乎是可以忽略不计的。总之,判断是否使用 clone:

  • 是否严肃的项目,玩具项目直接用 clone就行,简单不好吗?
  • 要看所在的代码路径是否是热点路径(hot path),例如执行次数较多的显然就是热点路径,热点路径就值得去使用性能更好的实现方式

上面的代码总感觉差点意思,特别是从OO语言来的!目标:通过构造函数来初始化一个 Config 实例,而不是直接通过函数返回实例,典型的,标准库中的 String::new 函数就是一个范例。

//in main.rs//引入环境包,可以处理传入的参数use std::env;use std::fs;fn main() {    //将内容转换为数组集合    let args: Vec = env::args().collect();    //待搜查字符串    //存储文件路径    //let query = &args[1];    //let file_path = &args[2];    //2.0    //let config = parse_config(&args);    let config = Config::new(&args);    println!("Searching for {}", config.query);    println!("In file {}", config.file_path);    //读取文件内容,记得异常处理    let context =        fs::read_to_string(config.file_path).expect("Should have been able to read the file!");    //捕获变量    print!("With text:\n{context}");    //使用 dbg!宏,读取数组内容    //dbg!(args);}// in main.rs//聚合配置变量struct Config {    query: String,    file_path: String,}// 命令解析//这里选择clone, 防止所有权带来的问题/*fn parse_config(args: &[String]) -> Config {   let query = args[1].clone();   let file_path = args[2].clone();   Config {query,file_path}}*///修改为符合 OO 语言的构造impl Config {    fn new(args: &[String]) -> Config {        let query = args[1].clone();        let file_path = args[2].clone();        Config { query, file_path }    }}

错误处理

如果用户不输入任何命令行参数,我们的程序会怎么样?结果喜闻乐见,由于 args 数组没有任何元素,因此通过索引访问时,会直接报出数组访问越界的 panic。

想法一:主动调用panic

impl Config {    fn new(args: &[String]) -> Config {        //错误处理1:主动调用panic        if args.len( ) < 3 {            panic!("not enough arguments");        }        let query = args[1].clone();        let file_path = args[2].clone();        Config { query, file_path }    }}

用户看到了更为明确的提示,但是还是有一大堆 debug 输出,这些我们其实是不想让用户看到的。这么看来,想要输出对用户友好的信息, panic 是不太适合的,它更适合告知开发者,哪里出现了问题

想法二:返回Result来代替直接panic

new往往不会失败,毕竟新建一个实例没道理失败,对不?因此修改为build` 会更加合适。

//in main.rs//引入环境包,可以处理传入的参数use std::env;use std::fs;use std::process;fn main() {    //将内容转换为数组集合    let args: Vec = env::args().collect();    //待搜查字符串    //存储文件路径    //let query = &args[1];    //let file_path = &args[2];    //2.0    //let config = parse_config(&args);    //更加友好的错误处理    let config = Config::build(&args).unwrap_or_else(|err| {        println!("Problem parsing arguments: {err}");        process::exit(1);    });    println!("Searching for {}", config.query);    println!("In file {}", config.file_path);    //读取文件内容,记得异常处理    let context =        fs::read_to_string(config.file_path).expect("Should have been able to read the file!");    //捕获变量    print!("With text:\n{context}");    //使用 dbg!宏,读取数组内容    //dbg!(args);}// in main.rs//聚合配置变量struct Config {    query: String,    file_path: String,}// 命令解析//这里选择clone, 防止所有权带来的问题/*fn parse_config(args: &[String]) -> Config {   let query = args[1].clone();   let file_path = args[2].clone();   Config {query,file_path}}*///修改为符合 OO 语言的构造impl Config {                                  //标注生命周期,让闭包可以处理    fn build(args: &[String]) -> Result {        //错误处理1:主动调用panic        /*        if args.len( ) < 3 {            panic!("not enough arguments");        }        */        //错误处理2:返回Result来直接代替panic        if args.len() < 3 {            return Err("not enough arguments");        }        let query = args[1].clone();        let file_path = args[2].clone();        Ok(Config { query, file_path })    }}

分离主体逻辑

继续精简 main 函数,那就是将主体逻辑( 例如业务逻辑 )从 main 中分离出去,这样 main 函数就保留主流程调用,非常简洁。只负责解析命令。

注意:这里因为config直接由主体逻辑负责了,所以采用所有权进行转移。

fn main() {    //将内容转换为数组集合    let args: Vec = env::args().collect();    //待搜查字符串    //存储文件路径    //let query = &args[1];    //let file_path = &args[2];    //2.0    //let config = parse_config(&args);    //更加友好的错误处理    let config = Config::build(&args).unwrap_or_else(|err| {        println!("Problem parsing arguments: {err}");        process::exit(1);    });    println!("Searching for {}", config.query);    println!("In file {}", config.file_path);    run(config)    //使用 dbg!宏,读取数组内容    //dbg!(args);}//直接所有权转移fn run(config: Config) {    //读取文件内容,记得异常处理    let context =        fs::read_to_string(config.file_path).expect("Should have been able to read the file!");    //捕获变量    print!("With text:\n{context}");}

使用?和特征对象来返回错误

run` 函数没有错误处理,因为在文章开头我们提到过,错误处理最好统一在一个地方完成,这样极其有利于后续的代码维护。

//in main.rs//引入环境包,可以处理传入的参数use std::env;use std::error::Error;use std::fs;use std::process;fn main() {    //将内容转换为数组集合    let args: Vec = env::args().collect();    //待搜查字符串    //存储文件路径    //let query = &args[1];    //let file_path = &args[2];    //2.0    //let config = parse_config(&args);    //更加友好的错误处理    let config = Config::build(&args).unwrap_or_else(|err| {        println!("Problem parsing arguments: {err}");        process::exit(1);    });    println!("Searching for {}", config.query);    println!("In file {}", config.file_path);        //因为只需要匹配一个,用if let 进行匹配错误,不用match    if let Err(e) = run(config) {        println!("Application error: {e}");        process::exit(1);    }    //使用 dbg!宏,读取数组内容    //dbg!(args);}//直接所有权转移//Error是特征对象,引入包,dyn类型,动态,智能指针fn run(config: Config) -> Result<(), Box>{    //读取文件内容,记得异常处理    //使用 ? 将otherError转换为returnError    let context =        fs::read_to_string(config.file_path)?;    //捕获变量    print!("With text:\n{context}");    Ok(())}// in main.rs//聚合配置变量struct Config {    query: String,    file_path: String,}// 命令解析//这里选择clone, 防止所有权带来的问题/*fn parse_config(args: &[String]) -> Config {   let query = args[1].clone();   let file_path = args[2].clone();   Config {query,file_path}}*///修改为符合 OO 语言的构造impl Config {    fn build(args: &[String]) -> Result {        //错误处理1:主动调用panic        /*        if args.len( ) < 3 {            panic!("not enough arguments");        }        */        //错误处理2:返回Result来直接代替panic        if args.len() < 3 {            return Err("not enough arguments");        }        let query = args[1].clone();        let file_path = args[2].clone();        Ok(Config { query, file_path })    }}

分离逻辑代码到库包中

首先,创建一个 src/lib.rs 文件,然后将所有的非 main 函数都移动到其中。

记得:分离之后,要对库文件里的各个代码标记上权限等。

libc.rs

//引入到库包中,非main的代码use std::error::Error;use std::fs;//聚合配置变量pub struct Config {    pub query: String,    pub file_path: String,}// 命令解析//这里选择clone, 防止所有权带来的问题/*fn parse_config(args: &[String]) -> Config {   let query = args[1].clone();   let file_path = args[2].clone();   Config {query,file_path}}*///修改为符合 OO 语言的构造impl Config {    pub fn build(args: &[String]) -> Result {        //错误处理1:主动调用panic        /*        if args.len( ) < 3 {            panic!("not enough arguments");        }        */        //错误处理2:返回Result来直接代替panic        if args.len() < 3 {            return Err("not enough arguments");        }        let query = args[1].clone();        let file_path = args[2].clone();        Ok(Config { query, file_path })    }}//直接所有权转移//Error是特征对象,引入包,dyn类型,动态,智能指针pub fn run(config: Config) -> Result<(), Box>{    //读取文件内容,记得异常处理    //使用 ? 将otherError转换为returnError    let context =        fs::read_to_string(config.file_path)?;    //捕获变量    print!("With text:\n{context}");    Ok(())}

main.rs

//in main.rs//引入环境包,可以处理传入的参数use std::env;use std::process;use minigrep::Config;fn main() {    //将内容转换为数组集合    let args: Vec = env::args().collect();    //待搜查字符串    //存储文件路径    //let query = &args[1];    //let file_path = &args[2];    //2.0    //let config = parse_config(&args);    //更加友好的错误处理    let config = Config::build(&args).unwrap_or_else(|err| {        println!("Problem parsing arguments: {err}");        process::exit(1);    });    println!("Searching for {}", config.query);    println!("In file {}", config.file_path);        //因为只需要匹配一个,用if let 进行匹配错误,不用match    if let Err(e) = minigrep::run(config) {        println!("Application error: {e}");        process::exit(1);    }    //使用 dbg!宏,读取数组内容    //dbg!(args);}

这里的 mingrep::run 的调用,以及 Config 的引入,跟使用其它第三方包已经没有任何区别,也意味着我们成功的将逻辑代码放置到一个独立的库包中,其它包只要引入和调用就行。

这里,lib.rs就是库,库的名就是项目名。本身也是一个包。

测试驱动开发

我们需要先编写一些测试代码,也是最近颇为流行的测试驱动开发模式(TDD, Test Driven Development):

  1. 编写一个注定失败的测试,并且失败的原因和你指定的一样
  2. 编写一个成功的测试
  3. 编写你的逻辑代码,直到通过测试

注定失败的用例

pub fn search<"a >(query: &str,contents: &"a str) -> Vec<&"a str> {    vec![]}#[cfg(test)]mod tests {    use super::*;    #[test]    fn one_result( ) {        let query = "duct";        let contents = "\        Rust:safe, fast, productive.        Pick three.        ";        assert_eq!(vec!["safe,fast,productive."],search(query,contents));    }}
$ cargo test   Compiling minigrep v0.1.0 (file:///projects/minigrep)    Finished test [unoptimized + debuginfo] target(s) in 0.97s     Running unittests src/lib.rs (target/debug/deps/minigrep-9cd200e5fac0fc94)running 1 testtest tests::one_result ... FAILEDfailures:---- tests::one_result stdout ----thread "main" panicked at "assertion failed: `(left == right)`  left: `["safe, fast, productive."]`, right: `[]`", src/lib.rs:44:9note: run with `RUST_BACKTRACE=1` environment variable to display a backtracefailures:    tests::one_resulttest result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00serror: test failed, to rerun pass `--lib`

注定成功的用例

pub fn search<"a >(query: &str,contents: &"a str) -> Vec<&"a str> {    //vec![] //注定失败的用例    let mut result = Vec::new();    for line in contents.lines() {        if line.contains(query) {            result.push(line);        }    }    result}
$ cargo test   Compiling minigrep v0.1.0 (file:///projects/minigrep)    Finished test [unoptimized + debuginfo] target(s) in 1.22s     Running unittests src/lib.rs (target/debug/deps/minigrep-9cd200e5fac0fc94)running 1 testtest tests::one_result ... oktest result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s     Running unittests src/main.rs (target/debug/deps/minigrep-9cd200e5fac0fc94)running 0 teststest result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s   Doc-tests minigreprunning 0 teststest result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

在run函数中调用search函数

//直接所有权转移//Error是特征对象,引入包,dyn类型,动态,智能指针pub fn run(config: Config) -> Result<(), Box> {    //读取文件内容,记得异常处理    //使用 ? 将otherError转换为returnError    let contents = fs::read_to_string(config.file_path)?;    //捕获变量    //print!("With text:\n{contents}");    for line in search(&config.query, &contents) {        println!("{line}");    }    Ok(())}
$ cargo run -- frog poem.txt   Compiling minigrep v0.1.0 (file:///projects/minigrep)    Finished dev [unoptimized + debuginfo] target(s) in 0.38s     Running `target/debug/minigrep frog poem.txt`How public, like a frog

使用环境变量来增强程序

在编译时候控制,如栈展开,大小写敏感

RUST_BACKTRACE=1 cargo run
IGNORE_CASE=1 cargo run -- to poem.txt

首先写一个注定失败

很简单,函数不要实现就好

写一个成功的案例

//大小写敏感pub fn search<"a >(query: &str,contents: &"a str) -> Vec<&"a str> {    //vec![] //注定失败的用例    let mut result = Vec::new();    for line in contents.lines() {        if line.contains(query) {            result.push(line);        }    }    result}//大小写不敏感pub fn search_case_insensitive<"a >(query: &str,contents: &"a str) -> Vec<&"a str> {    let query = query.to_lowercase();    let mut result = Vec::new();    for line in contents.lines() {        if line.to_lowercase().contains(&query) {            result.push(line);        }    }    result}#[cfg(test)]mod tests {    use super::*;        #[test] //标注为test    fn case_sensitive( ) {        let query = "duct";        let contents = "\Rust:safe, fast, productive.Pick three.Duct tape.";        assert_eq!(vec!["safe, fast, productive."],search(query,contents));    }         #[test]    fn case_insensitive( ) {        let query = "rUsT";        let contents = "\Rust:safe, fast, productive. Pick three. Trust me.";        assert_eq!(vec!["Rust:", "Trust me."],search_case_insensitive(query, contents));    }}

环境控制大写与内部流程控制

//直接所有权转移//Error是特征对象,引入包,dyn类型,动态,智能指针pub fn run(config: Config) -> Result<(), Box> {    //读取文件内容,记得异常处理    //使用 ? 将otherError转换为returnError    let contents = fs::read_to_string(config.file_path)?;    //捕获变量    //print!("With text:\n{contents}");    let results = if config.ignore_case {        search_case_insensitive(&config.query, &contents)     } else {        search(&config.query, &contents)    };    for line in results {        println!("{line}");    }    Ok(())}
//聚合配置变量pub struct Config {    pub query: String,    pub file_path: String,    pub ignore_case: bool,}

重定向错误信息的输出

无论 debug 还是 error 类型,都是通过 println! 宏输出到终端的标准输出( stdout ),但是对于程序来说,错误信息更适合输出到标准错误输出(stderr)。用户就可以选择将普通的日志类信息输出到日志文件 1,然后将错误信息输出到日志文件 2,甚至还可以输出到终端命令行。

结论:分离错误信息

如果错信息输出到标准输出,那么它们将跟普通的日志信息混在一起,难以分辨,因此我们需要将错误信息进行单独输出。

标准错误输出stderr

将错误信息和日志信息,在终端输出其他内容,在新的文件中出现很简单,将改为eprintln即可

fn main() {    //将内容转换为数组集合    let args: Vec = env::args().collect();    //待搜查字符串    //存储文件路径    //let query = &args[1];    //let file_path = &args[2];    //2.0    //let config = parse_config(&args);    //更加友好的错误处理    let config = Config::build(&args).unwrap_or_else(|err| {        eprintln!("Problem parsing arguments: {err}");        process::exit(1);    });    //println!("Searching for {}", config.query);    //println!("In file {}", config.file_path);        //因为只需要匹配一个,用if let 进行匹配错误,不用match    if let Err(e) = minigrep::run(config) {        eprintln!("Application error: {e}");        process::exit(1);    }    //使用 dbg!宏,读取数组内容    //dbg!(args);}

完结+迭代器修改

一些后续不再使用的,自己传入迭代器用迭代器去处理遍历,让语言变得更rusty

main.rs

//in main.rs//引入环境包,可以处理传入的参数use std::env;use std::process;use minigrep::Config;fn main() {    //将内容转换为数组集合    //let args: Vec = env::args().collect();    //待搜查字符串    //存储文件路径    //let query = &args[1];    //let file_path = &args[2];    //2.0    //let config = parse_config(&args);    //更加友好的错误处理    //这里直接传入迭代器即可    let config = Config::build(env::args()).unwrap_or_else(|err| {        eprintln!("Problem parsing arguments: {err}");        process::exit(1);    });    //println!("Searching for {}", config.query);    //println!("In file {}", config.file_path);        //因为只需要匹配一个,用if let 进行匹配错误,不用match    if let Err(e) = minigrep::run(config) {        eprintln!("Application error: {e}");        process::exit(1);    }    //使用 dbg!宏,读取数组内容    //dbg!(args);}

lib.rs

//引入到库包中,非main的代码use std::error::Error;use std::fs;use std::env;//聚合配置变量pub struct Config {    pub query: String,    pub file_path: String,    pub ignore_case: bool,}// 命令解析//这里选择clone, 防止所有权带来的问题/*fn parse_config(args: &[String]) -> Config {   let query = args[1].clone();   let file_path = args[2].clone();   Config {query,file_path}}*///修改为符合 OO 语言的构造impl Config {    //由于修改了clone,所以这里要修改    //而且移除索引    pub fn build(mut args : impl Iterator) -> Result {        //错误处理1:主动调用panic        /*        if args.len( ) < 3 {            panic!("not enough arguments");        }        */        //错误处理2:返回Result来直接代替panic        /*         if args.len() < 3 {            return Err("not enough arguments");        }        */        args.next();        let query = match args.next() {            Some(arg) => arg,            None => return Err("Didn"t get a query string"),        };        let file_path = match args.next() {            Some(arg) => arg,            None => return Err("Didn"t get a file path")        };        let ignore_case = env::var("IGNORE_CASE").is_ok();                //let query = args[1].clone();        //let file_path = args[2].clone();        //klet ignore_case = env::var("IGNORE_CASE").is_ok();        Ok(Config { query, file_path , ignore_case})    }}//直接所有权转移//Error是特征对象,引入包,dyn类型,动态,智能指针pub fn run(config: Config) -> Result<(), Box> {    //读取文件内容,记得异常处理    //使用 ? 将otherError转换为returnError    let contents = fs::read_to_string(config.file_path)?;    //捕获变量    //print!("With text:\n{contents}");    let results = if config.ignore_case {        search_case_insensitive(&config.query, &contents)     } else {        search(&config.query, &contents)    };    for line in results {        println!("{line}");    }    Ok(())}//大小写敏感pub fn search<"a >(query: &str,contents: &"a str) -> Vec<&"a str> {    //vec![] //注定失败的用例    /*     let mut result = Vec::new();    for line in contents.lines() {        if line.contains(query) {            result.push(line);        }    }    result    */    contents        .lines()        .filter(|line| line.contains(query))        .collect()}//大小写不敏感pub fn search_case_insensitive<"a >(query: &str,contents: &"a str) -> Vec<&"a str> {    let query = query.to_lowercase();    /*    let mut result = Vec::new();    for line in contents.lines() {        if line.to_lowercase().contains(&query) {            result.push(line);        }    }    result    */    contents        .lines()        .filter(|line| line.to_lowercase().contains(query.as_str()))        .collect()}#[cfg(test)]mod tests {    use super::*;        #[test] //标注为test    fn case_sensitive( ) {        let query = "duct";        let contents = "\Rust:safe, fast, productive.Pick three.Duct tape.";        assert_eq!(vec!["safe, fast, productive."],search(query,contents));    }         #[test]    fn case_insensitive( ) {        let query = "rUsT";        let contents = "\Rust:safe, fast, productive. Pick three. Trust me.";        assert_eq!(vec!["Rust:", "Trust me."],search_case_insensitive(query, contents));    }}

测试数据

I"m nobody! Who are you?我啥也不是,你呢?Are you nobody, too?牛逼如你也是无名之辈吗?Then there"s a pair of us - don"t tell!那我们就是天生一对,嘘!别说话!They"d banish us, you know.你知道,我们不属于这里。How dreary to be somebody!因为这里属于没劲的大人物!How public, like a frog他们就像青蛙一样呱噪,To tell your name the livelong day成天将自己的大名To an admiring bog!传遍整个无聊的沼泽!
上一篇:媒体关注丨无锡太湖水治理:走出了一条全新的探索之路 下一篇:最后一页
x
推荐阅读

一起来学rust|简单的mingrep

2023-05-14

媒体关注丨无锡太湖水治理:走出了一条全新的探索之路

2023-05-14

普尔:卢尼整个赛季都发挥出色 他配得上所有的赞扬

2023-05-14

唐太宗统治时期被称为什么_唐太宗属于什么号

2023-05-14

九州天空城电视剧草民_九州天空城电视剧全集免费吉吉影音 聚看点

2023-05-14

30浏览器删除快速信息_即时

2023-05-14

黄河的资料简介200字_黄河的资料简介

2023-05-14

金钟大帮助被霸凌女生 帮助报警并陪同其等待父母|天天精选

2023-05-14

手持挂烫机哪个牌子好_熨烫机哪个牌子好 环球热资讯

2023-05-14

叉尾斗鱼怎么分公母图片_叉尾斗鱼怎么分公母

2023-05-14

当前焦点!家庭怎么用烤箱做烤鱼

2023-05-14

贵阳去梵净山多少公里_贵阳到梵净山多少公里|环球快播报

2023-05-14

韩网热议T1击败GEN:Faker好厉害!亚运会中单还要选chovy吗? 时快讯

2023-05-14

小米盒子怎么看电视直播_小米盒子看电视直播的方法 聚看点

2023-05-14

难舍难分的心情句子_难舍难分的心情句子有什么_新要闻

2023-05-14

澧县涔南镇开展防灾减灾宣传周系列活动

2023-05-13

A股缩量到万亿以下,意味着什么? 每日热门

2023-05-13

每日头条!糖,碳水,脂肪!哪个更容易使人发胖?

2023-05-13

从上海坐公交到北京_从上海坐公交到北京|精彩看点

2023-05-13

国家防办、应急管理部:加强对中小河流等重点部位巡查防守 每日看点

2023-05-13

习作:故事新编评课说课稿教学反思点评-环球聚看点

2023-05-13

这很足协!足协五一前曾布置作业,工作人员须完成小作文 焦点速读

2023-05-13

世界快看点丨意甲争四大混战!拉齐奥两轮不胜 米兰双雄有机会

2023-05-13

北湖交警携手外卖骑手开展“一盔一带”安全宣传活动

2023-05-13

世界时讯:荣耀新平板曝光:13英寸大屏 骁龙888芯片

2023-05-13

能有用?媒体人:足协五一前曾布置作业,工作人员须完成小作文-环球信息

2023-05-13

微速讯:江西赣州:中国人才吸引力城市排行榜的黑马

2023-05-13

在香港注册公司的好处 在香港注册公司

2023-05-13

世界头条:“少年急救官再出发”主题公益活动全国启动仪式在成都举行

2023-05-13

28年从未放弃 江西父亲来厦寻子

2023-05-13

环球观点:建筑设计院真实待遇怎么样(建筑设计院)

2023-05-13

什么是食品添加剂_食品添加剂的作用_环球热推荐

2023-05-13

肾错构瘤饮食禁忌三种_右肾错构瘤是什么意思_即时

2023-05-13

【中国网评】芯片禁令反噬美企,所谓“围堵中国”徒增笑耳

2023-05-12

金太阳:5月11日公司高管刘宜彪减持公司股份合计3000股

2023-05-12

当前快讯:哈尔滨太平海事处联合5家客运公司开展水上综合应急演练

2023-05-12

依然的近义词和反义词 依然的近义词 每日热文

2023-05-12

世界快资讯丨市小客车指标办昨天发布今年第3期小客车指标申请审核结果

2023-05-12

【全球播资讯】韩媒:日本无视舆论强行排放福岛核污水 应堂堂正正听取国际呼声

2023-05-12

今日聚焦!铁矿石价格“虚火”渐退,价格连跌降至年内低点

2023-05-12

天津资格型人才落户政策咨询电话是多少? 焦点信息

2023-05-12

看点:特斯拉召回超110万辆存在安全隐患汽车

2023-05-12

《闪电侠》电影时长曝光:2小时30分钟过足眼瘾!|今日热讯

2023-05-12

关注:【工作动态】太原城管指挥中心组织市政建管中心开展新媒体宣传及信息报送培训

2023-05-12

环球速递!安徽两位市委书记,同日“逛夜市”

2023-05-12

【世界时快讯】α-甲基苯乙烯商品报价动态(2023-05-12)

2023-05-12

世界通讯!专家建议给生孩子的人发薪:生育本身就是一份工作

2023-05-12

​2021大众Atlas将继续使用过于熟悉的发动机选项-环球聚看点

2023-05-12

世界讯息:北京海洋公园施工厂家_北京海洋公园

2023-05-11

巴厘岛命案已形成初步报告,印尼官方回应:正全力调查

2023-05-11

Vlog|探访雄安站

2023-05-11

环球最新:国联股份:截至今年年底,公司计划累计完成100家云工厂签约

2023-05-11

121期唐龙排列三预测奖号:奇偶比大小比分析|最新

2023-05-11

二十四小时播报:高铁掌掴事件东北大哥 高铁掌掴事件东北大哥再回应

2023-05-11

谷歌XTAITO推经典游戏《太空侵略者》AR版 最新谷歌技术打造

2023-05-11

全球快消息!魔方的玩法口诀图解_长条魔方玩法图解

2023-05-11

“中国荔乡”广东茂名做大做优荔枝单品“妃子笑”-环球动态

2023-05-11

白鹤滩水电站位于四川省宁南县和云南省巧家县境内_白鹤滩水电站工程概况 世界视讯

2023-05-11

系列赛湖人罚球领先8&20&23个时均赢球 领先1个+持平时均输球

2023-05-11

云顶香港上市地位将于5月16日起取消

2023-05-11

理想,不跟蔚来小鹏做兄弟

2023-05-11

【世界热闻】周生生铂金多少钱一克(2023年05月11日)参考价格

2023-05-11

济青列入灵活就业人员缴纳公积金全国试点

2023-05-11

焦点播报:前中超外援加盟豪门?意媒:阿诺托维奇不去罗马 接近转投米兰

2023-05-11

天天视点!“股神”又买对了!巴菲特投资的日本商社利润创新高,股价涨破纪录了

2023-05-11

【环球新视野】争分夺秒!高三老师们走廊摆摊式辅导为学生答疑,网友:怀念那段拼搏的时光

2023-05-11

环球速递!期市开盘:商品期货多数下跌,沪镍跌超4%,纯碱、铁矿石等跌超2%

2023-05-11

世界要闻:彩虹股份:5月10日融资买入668.57万元,融资融券余额4.21亿元

2023-05-11

大族数控05月10日获深股通增持6.63万股

2023-05-11

速看:房子五证齐全是哪五证图片(五证齐全是哪五证)

2023-05-11

保罗-里德谈自由市场:如果球队愿意留下我 那么我肯定会回来

2023-05-11

04月05日芜湖前往博尔塔拉出行防疫政策查询-从芜湖出发到博尔塔拉的防疫政策-天天视讯

2023-05-11

新型举国体制是什么意思_体制是什么意思 天天热讯

2023-05-11

遥感技术应用领域有哪些_遥感技术的应用领域-焦点日报

2023-05-11

2023年轻薄高性价比Windows笔记本,哪款更值得入手?|焦点热闻

2023-05-10

证券日报:亏损公司的高管应该享受高薪吗?

2023-05-10

陈果:今年A股是局部牛,AI下半年有核心催化,关注硬件和垂直应用,中特估依然重要

2023-05-10

广州64.7亿元挂牌5宗土地 涉两宗广信资产包地块 每日热点

2023-05-10

全球讯息:长三角G60科创走廊数字经济赋能先进制造高质量发展大会举行

2023-05-10

河南省第十四届运动会倒计时100天-天天快报

2023-05-10

中国星辰 | 瞄准今天21时22分 天舟六号任务将实现双“首发” 世界简讯

2023-05-10

当前消息!真我11 Pro+体验报告:是轻奢拍照旗舰,又是便宜的奢侈品

2023-05-10

北京邮电大学成立卓越工程师学院

2023-05-10

西方多国就美国枪支暴力发出旅行警告

2023-05-10

【热闻】国美电器再被执行2125万

2023-05-10

两岸青山相对出下一句_这句话出自哪里

2023-05-10

养什么肉狗好_肉狗养殖什么品种好

2023-05-10

精选!王菲又要带断货了,穿千元风衣拿30元包,现身巴黎巨星气场不减

2023-05-10

母亲节应送母亲什么 母亲节应送母亲什么礼物手工

2023-05-10

开源证券:预计2023年全球光伏新增装机或达350GW

2023-05-10

42秒触顶!南京第二轮土拍热度不减...

2023-05-10

全球百事通!2岁男童喉咙长菜花样肿块确诊感染HPV

2023-05-10

焦点速看:仰望 U8 最新实拍!比奔驰 GLS 更大 预售 109.8 万元

2023-05-10

全球聚焦:新车 | 18.98万元起,新增700公里尊贵型,比亚迪海豹冠军版上市

2023-05-10

欠条需要在法律上怎么写 世界热头条

2023-05-10

莆田电信:破解乡村治理能力薄弱问题

2023-05-10

电影《我和妈妈的最后一年》5月12日正式与观众见面,全新视角诠释有裂痕的亲子关系 环球快资讯

2023-05-10

首发狂轰54分,一度领先15分!恩比德得分王,塔图姆后知后觉|焦点消息

2023-05-10

全球滚动:大连首个!大连金石滩景区获评国家级文明旅游示范单位

2023-05-10

世界热消息:中铁高铁电气装备股份有限公司关于2022年年度报告的更正公告

2023-05-10