最新要闻
- 【焦点热闻】好莱坞编剧大罢工:GPT技术引发激烈争议
- 经典28定律?苹果在中国手机市场份额2成 赚走8成利润|每日简讯
- 电池只能充电500次?别太荒谬!收下这份真正的充电秘籍
- 【全球新要闻】摊主买彩票中2千多万激动砸摊位:称21人合买 明天就分钱
- 每日精选:惠普打印机禁用非原装墨盒:官方称是为用户安全考虑
- 当前焦点!地狱笑话?大学母亲节配图是《进击的巨人》
- 泥鳅汤做法_泥鳅汤的烹饪方法 环球实时
- 已在轨生活160多天 航天员费俊龙从太空发回对母亲的节日祝福
- 热播电视剧将本科写成大专 高校不干了 官方道歉|天天聚看点
- 【热闻】首发紫光展锐T750!海信悄然推出F70 Lite手机
- 迭部县气象台发布大风蓝色预警信号【2023-05-14】
- DIY技巧:微星B760主板13600K降压教程 CPU温度暴降25℃
- 世界消息!专家称电动车要发展农村型号:支持反向充电 可增加收入
- 环球热门:1-2!“全校班”广州队又输了!3连败+开局5轮不胜,直冲降级区
- 天天观速讯丨安卓一年一迭代谷歌也累了:开始挤牙膏更新
- 女子把变心男友送的黄金卖了14万:没真心但有真金!自愿赠与或不用返还_天天快看
手机
iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?
警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案
- iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?
- 警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案
- 男子被关545天申国赔:获赔18万多 驳回精神抚慰金
- 3天内26名本土感染者,辽宁确诊人数已超安徽
- 广西柳州一男子因纠纷杀害三人后自首
- 洱海坠机4名机组人员被批准为烈士 数千干部群众悼念
家电
观点:一起来学rust|简单的mingrep
2023-5-14,完成于新冠病隔离期间。是我学习rust起步,意在通过这个小的项目,学习一些基本的rust的思想,熟悉rust的基本语法知识。
【资料图】
学习到的内容总结
- 关注分离点:把程序的启动放到main,主体逻辑移入库实现
- 聚合变量,将与某件事情有关的所有变量,聚合起来,同时也赋予其相应的方法
- 测试驱动的逻辑,先写一个绝对失败的例子,再写一个绝对成功的例子,编写你的代码逻辑,直到成功
- 使用环境变量,来增加程序的撸棒性
- 错误处理的两种逻辑:主动与被动
- 日志分离,普通日志与错误信息提示
- 迭代器遍历,更加简洁且完整,性能与安全性大幅度提升
- IO处理,要明白,一切用户输入都是不可信的输入
- 使用?返回特征对象,用Box来存储动态的特征对象指针
- 代码分离,对象抽象
Rust知名项目grep替代
https://github.com/BurntSushi/ripgrep
实现的功能
ps:查看cargo参数的方法,cargo --list
- input:指定文件和待查找的字符串
- output:返回查找结果
大体的形式
传入,--告诉后面的参数给程序使用而不是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 函数,这个不符合软件工程,没有错误处理,功能不完善等。不过没关系,万事开头难,好歹我们成功迈开了第一步。但凡稍微没那么糟糕的程序,都应该具有代码模块化和错误处理,不然连玩具都谈不上。梳理我们的代码和目标后,可以整理出大致四个改进点:
- 单一且庞大的函数。对于 minigrep 程序而言, main 函数当前执行两个任务:解析命令行参数和读取文件。但随着代码的增加,main 函数承载的功能也将快速增加。从软件工程角度来看,一个函数具有的功能越多,越是难以阅读和维护。因此最好的办法是将大的函数拆分成更小的功能单元。
- 配置变量散乱在各处。还有一点要考虑的是,当前 main 函数中的变量都是独立存在的,这些变量很可能被整个程序所访问,在这个背景下,独立的变量越多,越是难以维护,因此我们还可以将这些用于配置的变量整合到一个结构体中。
- 细化错误提示。 目前的实现中,我们使用 expect 方法来输出文件读取失败时的错误信息,这个没问题,但是无论任何情况下,都只输出 Should have been able to read the file 这条错误提示信息,显然是有问题的,毕竟文件不存在、无权限等等都是可能的错误,一条大一统的消息无法给予用户更多的提示。
- 使用错误而不是异常。 假如用户不给任何命令行参数,那我们的程序显然会无情崩溃,原因很简单:index out of bounds,一个数组访问越界的 panic,但问题来了,用户能看懂吗?甚至于未来接收的维护者能看懂吗?因此需要增加合适的错误处理代码,来给予使用者给详细友善的提示。还有就是需要在一个统一的位置来处理所有错误,利人利己!
改进程序(增加模块化和错误处理)
分离main函数思路(启动与逻辑)
如何处理庞大的 main 函数,Rust 社区给出了统一的指导方案:
- 将程序分割为 main.rs 和 lib.rs,并将程序的逻辑代码移动到后者内
- 命令行解析属于非常基础的功能,严格来说不算是逻辑代码的一部分,因此还可以放在 main.rs 中
按照这个方案,将我们的代码重新梳理后,可以得出 main 函数应该包含的功能:
- 解析命令行参数
- 初始化其它配置
- 调用 lib.rs 中的 run 函数,以启动逻辑代码的运行
- 如果 run 返回一个错误,需要对该错误进行处理
这个方案有一个很优雅的名字: 关注点分离(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 }) }}
- 当 Result 包含错误时,我们不再调用 panic 让程序崩溃,而是通过 process::exit(1) 来终结进程,其中 1 是一个信号值(事实上非 0 值都可以),通知调用我们程序的进程,程序是因为错误而退出的。
- unwrap_or_else是定义在 Result
上的常用方法,如果 Result 是 Ok,那该方法就类似 unwrap:返回 Ok 内部的值;如果是 Err,就调用闭包中的自定义代码对错误进行进一步处理
分离主体逻辑
继续精简 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 }) }}
- Result
的要求,因此使用了Ok(())返回一个单元类型()。最重要的是 Box, 如果按照顺序学到这里,大家应该知道这是一个Error 的特征对象(为了使用 Error,我们通过 use std::error::Error; 进行了引入),它表示函数返回一个类型,该类型实现了 Error 特征,这样我们就无需指定具体的错误类型,否则你还需要查看。 - ?传播界的大明星
- 最后,用if let处理返回的错误,我们并不关注 run 返回的 Ok 值,因此只需要用 if let 去匹配是否存在错误即可。
分离逻辑代码到库包中
首先,创建一个 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):
- 编写一个注定失败的测试,并且失败的原因和你指定的一样
- 编写一个成功的测试
- 编写你的逻辑代码,直到通过测试
注定失败的用例
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!传遍整个无聊的沼泽!
关键词:
-
关于Kubernetes-v1.23.6-网络组件-calico的安装部署...|焦点快看
当2个workernodes节点加入到& 160;Kubernetes k8s集群后,我们去master节点、执行kubectlgetnodes命令就可
来源: 观点:一起来学rust|简单的mingrep
关于Kubernetes-v1.23.6-网络组件-calico的安装部署...|焦点快看
【焦点热闻】好莱坞编剧大罢工:GPT技术引发激烈争议
经典28定律?苹果在中国手机市场份额2成 赚走8成利润|每日简讯
电池只能充电500次?别太荒谬!收下这份真正的充电秘籍
【全球新要闻】摊主买彩票中2千多万激动砸摊位:称21人合买 明天就分钱
CentOS7搭建keepalived+DRBD+NFS高可用共享存储
每日精选:惠普打印机禁用非原装墨盒:官方称是为用户安全考虑
当前焦点!地狱笑话?大学母亲节配图是《进击的巨人》
泥鳅汤做法_泥鳅汤的烹饪方法 环球实时
已在轨生活160多天 航天员费俊龙从太空发回对母亲的节日祝福
热播电视剧将本科写成大专 高校不干了 官方道歉|天天聚看点
【热闻】首发紫光展锐T750!海信悄然推出F70 Lite手机
迭部县气象台发布大风蓝色预警信号【2023-05-14】
three.js 入门学习(二) 环球热资讯
DIY技巧:微星B760主板13600K降压教程 CPU温度暴降25℃
世界消息!专家称电动车要发展农村型号:支持反向充电 可增加收入
环球热门:1-2!“全校班”广州队又输了!3连败+开局5轮不胜,直冲降级区
基于SLAM系统建图仿真,完成定位仿真
天天观速讯丨安卓一年一迭代谷歌也累了:开始挤牙膏更新
女子把变心男友送的黄金卖了14万:没真心但有真金!自愿赠与或不用返还_天天快看
升级彩超5项:瑞慈体检套餐279元母亲节大促 今日热搜
久穿不易变形 放克220g宽松短袖29元大促
徐工四款新“国货之光”问世:百变狮王、自动灭火机器人 国产化率100% 世界观焦点
万胜智能: 关于使用部分闲置募集资金进行现金管理的进展公告
美团一面:Spring Cloud 如何构建动态线程池?
动态焦点:每天走路超这一步数 能大幅降低死亡率 上班族学起来
B站“离谱”专利获批:开车也能发弹幕了?|新要闻
热议:俄媒:泽连斯基拒绝教皇方济各调解俄乌提议
世界热议:我对IdentityServer4的初步了解
每日关注!JavaSE面试题【长期更新】
想玩《塞尔达传说:王国之泪》却不知道买哪款Switch?这篇选购攻略帮你避坑!
CPU散片学问大:碰见这两个型号千万别买|今日热搜
01-Linux命令和C语言基础|全球快讯
全球今亮点!西湖5平米商亭租金284万 每天约7780元引热议
母亲节 我来讲一个给妈妈换了“苹果全家桶”后的故事
首次发现!唾液含剧毒的五爪金龙现身云南:寿命长达150年
每日短讯:2023年5月14日融雪剂价格最新行情预测
国金证券:稳增长政策效果加速显现 居民消费修复延续性较强
D加密沦陷!黑客放出《生化危机4重制版》破解资源:好评如潮大作免费玩
AI起了反效果:4月微软Bing市场份额不升反降
特斯拉雨天高速失控!旋转、掉头、撞墙后 司机接着加速跑了
【报资讯】怀旧服磨刀石是什么专业制作的(怀旧服磨刀石)
前端语言串讲 | 青训营笔记
女子夜里打出租 全程直播监控!司机:不自信了_当前焦点
环球快资讯丨最后一道封印解除!ChatGPT重大升级 上线联网功能
检察院不批捕取保候审后还会收监吗|世界快播
女子旅游后高烧不退确诊“不死癌症” 医生:晒太阳是重要诱因
性能完全不达标 EPA报告:特斯拉4680电池能量密度比2170还低 当前观察
landrover是什么车多少钱一辆 landrover是什么车
Python学习之六_同时访问Oracle和Mysql的方法
上海张江全链条发力营造更优企业创新发展环境 今日快讯
放弃ZEKU自研芯片!OPPO张璇:产品生命周期软件维护不受影响_世界速读
江苏扬州:体育嘉年华嗨出狂欢味 百余场赛事活动贯穿全年-全球播资讯
曾为中国最大的汽车经销商 庞大集团濒临退市
今天母亲节 妈妈收到孩子送礼物时的反应让千万网友动容-全球关注
每日焦点!自称长相比较可爱28岁女副教授回应带梗招生:院方支持新表达方式
Windows 10操作系统绝唱了!终极正式版开始强制升级
天天快讯:保定市区养犬收费标准来了!登记500/300,年检200!
学系统集成项目管理工程师(中项)系列21b_整体管理(下)
母亲节今天到来!微信上线限时状态:感谢妈妈 天天动态
卖给中国人的车 连玻璃都减配? 全球快看点
大哥13 Ultra同款!小米13/Pro相机界面升级:变焦转盘调焦更方便
成都市验房公司_成都验房公司
Java Socket编程|环球聚看点
Ubuntu下通过Wine安装LTSpice 17.1.8_当前讯息
环球聚焦:讯飞输入法推出苹果 macOS 版,支持 10.15 及以上版本
当前快看:江苏女子到山东旅游买到的特产竟是戒尺:自己之前根本没有见过
环球速读:80、90后的青春记忆!《街霸》过气了吗?
iPhone用户被骗子盯上!三招轻松破解
北京一车主遇无接触事故被认定负全责 骑车人自己滑倒:网友吵翻
百万召回能解决单踏板电门当刹车?特斯拉回应:选择权给大家 误踩会提醒
莱州市永安路街道:帮办代办暖心解忧 架起为民服务“连心桥”
23岁网红用GPT-4复制自己,每月狂赚3500万 当前播报
Prompt learning 教学[案例篇]:文生文案例设定汇总,你可以扮演任意角色进行专业分析-天天即时
七孔大豆纤维夏被到手59元:牛奶般丝滑 亲肤透气 今日报
何炅录制芒果TV《向往的生活》:手机真我11 Pro+抢镜
安卓机皇!三星Galaxy S23 Ultra限量版上市:9488元 全球速读
淄博八大局知名麻辣串疑被房东赶走:老板回应双方还在商讨此事 世界视讯
厦门英才学校小学部第三套课间操_厦门英才学校小学部-天天热消息
今日快讯:算命奇想
就没有《猫和老鼠》还原不了的图!AI被锤爆了
热门:中性笔后面的神秘液体是什么?竟然大有讲究!
今日讯!德州驴出肉率_德州驴
小米13 Pro被低估了!雷军力荐:数码发烧友就选它|观点
通讯!00后女生旅游不忘给新手机开光 网友:你是懂开光的
焦点资讯:《暗黑4》遇上DLSS 3:最低帧猛增50%
迁移到 Gradle 7.x 使用 Version Catalogs 管理依赖
焦点热议:线段树
【LeetCode剑指offer#04】包含min函数的栈、栈的压入、弹出序列(辅助栈的应用)
环球速读:51岁已被游客喊了十多年谭爷爷!熊猫饲养员谭金淘“出圈”
全球快讯:17岁少年骑共享单车52天5000公里!成都-拉萨-新疆 穿越无人区
当前快看:12岁男孩玩游戏一个月花掉10多万!家长申请退款却遭拒绝
聊一聊:iOS 16.5 RC准正式版推送!iOS 16系统更新要绝唱了
使用go-cqhttp搭建qq机器人
每天坐高铁上下班是什么体验?一个月2400元
拒绝爆显存!RTX 4060 Ti 16GB曝新料:功耗增加5W|环球观点
WSL Ubuntu 安装 minikube 世界热消息
工程粉墙合同范本(实用4篇)
环球新资讯:长征九号、长征十号火箭发动机好消息!200吨、载人可复用