当前位置:   article > 正文

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

rust 迭代器作为参数

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})
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

在这里我们使用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);
    });
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
fn main() {
    let config = Config::new(env::args()).unwrap_or_else(|err|{
        eprintln!("Problem parsing arguments:{}",err);
        process::exit(1);
    });
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

将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> {
  • 1

以迭代器作为参数更新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})
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

注意: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
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

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

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

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

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

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

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

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/凡人多烦事01/article/detail/702795
推荐阅读
相关标签
  

闽ICP备14008679号