The Rust Programming Language - 第13章 Rust语言中的函数式语言功能:迭代器与闭包 - 13.3 改进I/O项目

13 Rust语言中的函数式语言功能:迭代器与闭包

函数式编程风格通常包括将函数作为另一个函数的参数、返回值,将函数作为值赋值给变量,以供后续执行

本章中我们将会介绍以下内容:

闭包:一个可以存储在变量里的类似函数的数据结构

迭代器:一种处理元素序列的方式

如何使用这些功能来改进第十二章的I/O项目

这两个功能的性能(剧透警告:它们的速度超乎你的想象)

我40米的大刀已经饥渴难耐了,让我们攻下这一章!

13.3 改进I/O项目

上一节我们学习了迭代器相关的知识,现在我们来对上述代码进行改进,让其变的更加简洁

使用迭代器并去掉clone

我们先来回顾一下以前的一个例子

struct Config {
    query:String,
    filename:String,
}

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 = env::var(CASE_INSENSITIVE).is_err();

        Ok(Config{query,filename,case_sensitive})
    }
}

在这里我们使用clone,以便Config结构体可以拥有这些值,但是呢,这样调用是非常低效的

这里需要clone的原因是参数args 是借用的,new函数实际并不拥有它,因此为了返回Config实例的所有权,我们就克隆了Config结构体里面的两个字段

但是,我们学习了迭代器!可以想到是哪个功能可以解决这个问题吗?没错,就是迭代器可以捕获环境,让我们来详细研究一下如何修复这个问题

我们把new函数的参数改为获取一个有所有权的迭代器而不是借用的slice,ok,问题就这么被解决了,我们来看看实际例子

不过,要注意:使用迭代器前检查slice长度和索引特定位置的代码。这会明确Config::new的工作因为迭代器会负责访问这些值

直接使用env::args返回的迭代器

fn main() {
    let args: Vec<String> = env::args().collect();

    let config = Config::new(&args).unwrap_or_else(|err| {
        eprintln!("Probleming parsing arguments: {}",err);

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

将env::args 的返回值传递给Config::new

env::args函数返回一个迭代器!不同于将迭代器的值收集到一个vector中接着传递一个slice给Config::new,现在我们直接将env::args返回的迭代器的所有权传递给Config::new

我们再来更新一下Config::new的定义

pub fn new(mut args: std::env::Args)->Result<Config,&'static str> {

以迭代器作为参数更新Config::new的签名

env::args 函数的标准库文档显示,它返回的迭代器类型为 std::env::Args,因此new函数的参数类型是 std::env::Args 而不是 &[String]。因为我们拥有args的所有权,并且将通过对其进行迭代而改变args,我们可以将mut关键字添加到args参数的规范中以使其可以改变

使用 Iterator trait代替索引

接下来,我们将修改Config::new的内容。因为std::env::Args实现了Iterator trait,因此我们知道可以对其调用next方法

impl Config {
    pub fn new(mut args: std::env::Args)->Result<Config,&'static str> {
        args.next();

        let query = match.next() {
            Some(arg) => arg,
            None => return Err("Didn't get a query string"),
        };
        let case_sensitive = env::var("CASE_INSENSITIVE").is_err();
         Ok(Config{query,filename,case_sensitive})
    }
}

注意:env::args返回的第一个值是程序的名称,我们希望忽略它并获取下一个值,所以首先调用next并不对返回值做任何操作

之后对希望放入Config中字段query调用next。如果next返回Some,使用match来提取其值。如果它返回None,则意味着没有提供足够的参数并通过Err值提早返回,对filename值进行同样的操作

使用迭代器适配器来使代码更为简明

I/O项目中其它可以利用迭代器的地方是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
}

可以通过使用迭代器适配器来编写更简明的代码

这样避免了一个可变中间results vector 的使用。函数式编程风格倾向于最小化可变状态的数量来使代码更简洁。去掉可变状态可能会使得将来进行并行搜索的增强变的更容易,因为我们不必管理results vector 的并发访问

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

回忆search函数的目的是返回所有contents中包含query的行,类似于之前filter例子,可以使用filter适配器只保留line.contains(query)返回true的那些行。接着使用collect将匹配收集到另一个vector中,这样就容易多了

接下来的逻辑就是在代码中选用哪种风格问题了,我们大部分工程师还是倾向于使用迭代器风格

下一节我们聊聊这两种实现的性能

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值