首页 > 其他分享 >【Rust自学】13.9. 使用闭包和迭代器改进IO项目

【Rust自学】13.9. 使用闭包和迭代器改进IO项目

时间:2025-01-23 16:28:55浏览次数:3  
标签:闭包 args new let IO 13.9 query line contents

13.9.0. 写在正文之前

Rust语言在设计过程中收到了很多语言的启发,而函数式编程对Rust产生了非常显著的影响。函数式编程通常包括通过将函数作为值传递给参数、从其他函数返回它们、将它们分配给变量以供以后执行等等。

在本章中,我们会讨论 Rust 的一些特性,这些特性与许多语言中通常称为函数式的特性相似:

  • 闭包
  • 迭代器
  • 使用闭包和迭代器改进I/O项目(本文)
  • 闭包和迭代器的性能

喜欢的话别忘了点赞、收藏加关注哦(加关注即可阅读全文),对接下来的教程有兴趣的可以关注专栏。谢谢喵!(=・ω・=)
请添加图片描述

13.9.1. 回顾

本篇文章会以第12章中的grep项目为例演示使用闭包和迭代器改进I/O项目,在此之前我们先回顾一下。

第12章要做一个实例的项目——一个命令行程序。这个程序是一个grep(Global Regular Expression Print),是一个全局正则搜索和输出的工具。它的功能是在指定的文件中搜索出指定的文字。

这个项目分为这么几步:

  • 接收命令行参数
  • 读取文件
  • 重构:改进模块和错误处理
  • 使用TDD(测试驱动开发)开发库功能
  • 使用环境变量
  • 将错误信息写入标准错误而不是标准输出

lib.rs:

```rust
use std::error::Error;  
use std::fs;  
  
pub struct Config {  
    pub query: String,  
    pub filename: String,  
    pub case_sensitive: bool,  
}  
  
impl Config {  
    pub fn new(args: &[String]) -> Result<Config, &'static str> {  
        if args.len() < 3 {  
            return Err("Not enough arguments");  
        }  
        let query = args[1].clone();  
        let filename = args[2].clone();  
        let case_sensitive = std::env::var("CASE_INSENSITIVE").is_err();  
        Ok(Config { query, filename, case_sensitive})  
    }  
}  
  
pub fn run(config: Config) -> Result<(), Box<dyn Error>> {  
    let contents = fs::read_to_string(config.filename)?;  
    let results = if config.case_sensitive {  
        search(&config.query, &contents)  
    } else {  
        search_case_insensitive(&config.query, &contents)  
    };  
    for line in results {  
        println!("{}", line);  
    }  
    Ok(())  
}  
  
pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {  
    let mut results = Vec::new();  
    for line in contents.lines() {  
        if line.contains(query) {  
            results.push(line);  
        }  
    }  
    results  
}  
  
pub fn search_case_insensitive<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {  
    let mut results = Vec::new();  
    let query = query.to_lowercase();  
    for line in contents.to_lowercase().lines() {  
        if line.contains(&query) {  
            results.push(line);  
        }  
    }  
    results  
}  
  
#[cfg(test)]  
mod tests {  
    use super::*;  
  
    #[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)  
        );  
    }  
}

main.rs:

use std::env;  
use std::process;  
use minigrep::Config;  
  
fn main() {  
    let args:Vec<String> = env::args().collect();  
    let config = Config::new(&args).unwrap_or_else(|err| {  
        eprintln!("Problem parsing arguments: {}", err);  
        process::exit(1);  
    });  
    if let Err(e) = minigrep::run(config) {  
        eprintln!("Application error: {}", e);  
        process::exit(1);  
    }  
}

13.9.2. new函数的改进

看一下lib.rs里的new函数:

impl Config {  
    pub fn new(args: &[String]) -> Result<Config, &'static str> {  
        if args.len() < 3 {  
            return Err("Not enough arguments");  
        }  
        let query = args[1].clone();  
        let filename = args[2].clone();  
        let case_sensitive = std::env::var("CASE_INSENSITIVE").is_err();  
        Ok(Config { query, filename, case_sensitive})  
    }  
}

其中的这两行:

let query = args[1].clone();  
let filename = args[2].clone();

使用了克隆的方法。这是因为传进去的参数是&[String],没有所有权,但是Config结构体要求持有所有权。只有使用克隆才能让Config拥有queryfilename的所有权,即使克隆会造成性能开销。

但在我们学过迭代器之后我们可以在new函数里直接使用迭代器作为它的参数从而获得所有权。我们还可以通过迭代器实现长度检查和索引,使new函数的责任范围更加明确。

new函数之前我们得先改main函数对输入参数的处理方法,原本是:

let args:Vec<String> = env::args().collect();  
let config = Config::new(&args).unwrap_or_else(|err| {  
    eprintln!("Problem parsing arguments: {}", err);  
    process::exit(1);  
});  

现在我们去掉collect方法,直接把env::args()所获得的参数传给new函数:

let config = Config::new(env::args()).unwrap_or_else(|err| {  
    eprintln!("Problem parsing arguments: {}", err);  
    process::exit(1);  
});  

env::args()的返回类型是std::env::Args,它实现了Iterator trait,所以是一个迭代器。

现在来修改new函数:

impl Config {  
    pub fn new(mut args: std::env::Args) -> Result<Config, &'static str> {  
        if args.len() < 3 {  
            return Err("Not enough arguments");  
        }  
        args.next();  
        let query = args.next().unwrap();  
        let filename = args.next().unwrap();  
        let case_sensitive = std::env::var("CASE_INSENSITIVE").is_err();  
        Ok(Config { query, filename, case_sensitive})  
    }  
}
  • 把形参args的类型改为std::env::Args,还得声明为可变变量加上mut,因为next方法是消耗性迭代器
  • 函数体里有一行只写了args.next();是因为env::args()获取的第一个值是程序的名称而不是参数,写args.next();就是为了跳过这个值。
  • 后面的queryfilename就依次使用next方法来获取即可,这时候的queryfilename就是拥有所有权的String。但是next的返回值是Option枚举,所以可以使用unwrap来解包。

13.9.3. Search函数的改进

目前的Search函数是这样的:

pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {  
    let mut results = Vec::new();  
    for line in contents.lines() {  
        if line.contains(query) {  
            results.push(line);  
        }  
    }  
    results  
}

contents.lines()返回的也是迭代器,我们在这里手动地判断是否包含关键字,也就是query所存储的字符串,如果包含就把这行放到Vector里,最后把Vector返回。

对于在迭代器中寻找符合某个要求的目标元素组成新的迭代器,可以使用filter方法:

pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {  
    contents.lines().filter(|line| line.contains(query)).collect()  
}

通过在闭包中使用contains来检查是否包含关键字就实现了同样的逻辑。

既然普通的搜索函数能使用迭代器,同样的,大小写不敏感的搜索函数也可以使用迭代器:

pub fn search_case_insensitive<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {  
    contents.to_lowercase()  
        .lines()  
        .filter(|line| line.contains(&query.to_lowercase()))  
        .collect()  
}

注意,query.to_lowercase()得加&,因为query.to_lowercase()会生成String类型,而contains方法接收&str,所以不能直接传query.to_lowercase(),只有传引用进去,也就是&query.to_lowercase()才能正确执行。

转换为&str不仅可以加&,当然也可以用as_str方法:

pub fn search_case_insensitive<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {  
    contents.to_lowercase()  
        .lines()  
        .filter(|line| line.contains(query.to_lowercase().as_str()))  
        .collect()  
}

不管从代码量还是可读性上比,使用filter的方法都更好。此外filter方法还减少了临时变量。消除可变状态(let mut results = Vec::new();)使我们可以在未来通过并行化来提升搜索效率,因为无需考虑并发访问results的安全问题了。

标签:闭包,args,new,let,IO,13.9,query,line,contents
From: https://blog.csdn.net/weixin_71793197/article/details/145251573

相关文章

  • 基于Python和uiautomation的Windows桌面自动化操作方案
    基于Python和uiautomation的Windows桌面自动化操作方案在日常开发和测试过程中,我们经常需要对Windows桌面应用程序进行自动化操作。本文将记录如何使用uiautomation库来实现这些操作,同时为了避免对主机的正常使用造成干扰,借助VMwareWorkstation虚拟机环境进行操作,并结合实际案例......
  • 蓝牙芯片HS6621CG-C丰富IO口资源集成红外编码语音采集功能超高性能已移植NFC例程支持
    2.4Ghz的soc蓝牙5.1芯片HS6621CC语音遥控/智能门锁M4F内核兼容NORDIC的2.4G私有协议超低功耗,丰富IO口资源集成红外编码语音采集功能超高性能已移植NFC例程支持语音蓝牙遥控智能门锁智能家居等应用简介:HS6621CxC是一种功耗优化的真正的片上系统(SOC)解决方案,既适用于蓝牙低能耗,也......
  • 15 分布式锁和分布式session
    在java中一个进程里面使用synchronized在new出来对象头信息中加锁,如果是静态方法中在加载的类信息中加锁(我们在锁的原理中讲过)。如果使用lock加锁可以自己指定。这些都是在同一个进程空间中的操作。如果在分布式环境中由于程序不在一个进程空间,就没办法使用这些原子性的元......
  • 自学StableDiffusion,一般人我还是劝你算了吧
    本期将从以下4个模块逐步讲解:......
  • deformable attention中生成初始采样点位置(init_weights或者_reset_parameters函数)
    def_reset_parameters(self):constant_(self.sampling_offsets.weight.data,0)"""初始化偏移量预测的偏置(bias),使得初始偏移位置犹如不同大小的方形卷积核组合"""#(8,)[0,pi/4,pi/2,3*pi/2,...,7*pi/4]thetas=torch.arange(self.n_heads,......
  • 绿色下载Adobe Audition(AU)专业的音频编辑和后期制作软件
    目录AdobeAU软件简介一、软件简介1.1软件功能1.2适用行业二、系统要求2.1操作系统2.2硬件配置2.3音频硬件三、安装步骤3.1下载AdobeAudition3.2安装软件AdobeAU软件简介AdobeAU(AdobeAudition)是一款专业的音频编辑和后期制作软件,广泛应用于音乐创......
  • Cookies,Session,Storage 封装方法,包含过期时间
    importCookiesfrom'js-cookie'importrouterfrom'@/router'constTokenKey='token'exportfunctiongetToken(){returnCookies.get(TokenKey)??''}exportfunctionsetToken(token:string){returnCoo......
  • 深入解析 MinIO 的扁平化 Bucket 设计:原理、实践与最佳操作指南
    言简意赅的讲解MinIO解决的痛点在现代分布式存储中,MinIO是一款以高性能和简单设计著称的对象存储系统。作为S3协议兼容的存储解决方案,它在处理海量对象时采用了扁平化设计的bucket,这种设计不同于传统文件系统中树形的目录结构。本文将详细讲解MinIO扁平化bucket的......
  • legged-robot关于locomotion、Navigation任务主要文章速览
    0.前言目前leggedrobot包括locomotion(怎么走)、navigation(往哪走)、人形机器人的wholebodycontrol以及基于机械臂的manipulation的任务。本文章特此记录一方面便于日后自己的温故学习,另一方面也便于大家的学习和交流。如有不对之处,欢迎评论区指出错误,你我共同进步学习!警告......
  • "Utilization of poller processes over 75%"告警
    Zabbix是一个开源的监控软件,它可以监控各种网络参数、服务器健康状态等。在Zabbix服务器中,StartPollers参数决定了同时运行多少个数据采集器(pollers)。每个poller负责从不同的监控项收集数据。调整StartPollers的值可以帮助优化Zabbix服务器在资源使用和监控性能之间的平......