Rust学习第十一天——函数式语言特性:迭代器和闭包

闭包(closures)

使用闭包创建抽象行为

什么是闭包

  • 闭包:可以捕获其所在环境的匿名函数

  • 闭包:

  • 是匿名函数

  • 保存为变量、作为参数

  • 可在一个地方创建闭包,然后再另一个上下文中调用闭包来完成运算

  • 可从其定义的作用域捕获值

例子-生成自定义运动计划的程序

  • 算法的逻辑不是重点,重点是算法中的计算过程需要几秒钟时间。

  • 目标:不让用户发生不必要的等待

  • 仅在必要时调用该算法

  • 只调用一次

  • 例子:

use std::thread;
use std::time::Duration;

fn main() {
    let simulated_user_specified_value = 10;
    let simulated_random_number = 7;
    
    generate_workout(simulated_user_specified_value,
    simulated_random_number);
}

fn simulated_expensive_calculation(inyensity: u32) -> u32 {
    println!("calculating slowly……");
    thread::sleep(Duration::from_secs(2));
    inyensity
}

fn generate_workout(intensity: u32, random_number: u32) {
    if intensity < 25 {
        println!(
            "Today, do {} pushups!",
            simulated_expensive_calculation(intensity)
        );
        println!(
            "Next, do {} situps!",
            simulated_expensive_calculation(intensity)
        );
    } else {
        if random_number == 3 {
            println!("Take a break today! Remember to stay hydrated!");
        } else {
            println!(
                "Today, run for {} minutes",
                simulated_expensive_calculation(intensity)
            );
        }
    }
}
  • 修改后:

use std::thread;
use std::time::Duration;

fn main() {
    let simulated_user_specified_value = 10;
    let simulated_random_number = 7;

    generate_workout(simulated_user_specified_value,
    simulated_random_number);
}

fn simulated_expensive_calculation(inyensity: u32) -> u32 {
    println!("calculating slowly……");
    thread::sleep(Duration::from_secs(2));
    inyensity
}

fn generate_workout(intensity: u32, random_number: u32) {

    let expensive_closure = |num| {
        println!("caculating slowly ……");
        thread::sleep(Duration::from_secs(2));
        num
    };

    if intensity < 25 {
        println!(
            "Today, do {} pushups!", expensive_closure(intensity));
        println!(
            "Next, do {} situps!", expensive_closure(intensity));
    } else {
        if random_number == 3 {
            println!("Take a break today! Remember to stay hydrated!");
        } else {
            println!(
                "Today, run for {} minutes", expensive_closure(intensity));
        }
    }
}

闭包类型推断和标注

闭包类型推断

  • 闭包不要求标注参数和返回值的类型

  • 闭包通常很短小,只在狭小的上下文中工作,编译器通常能推断出类型

  • 注意:闭包的定义最终只会为参数/返回值推断出唯一具体的类型

使用泛型参数和Fn Trait来存储闭包

继续对上次代码进行解决

  • 创建一个struct,它持有闭包及其调用结果。

  • 只会在需要结果时才会执行该闭包

  • 可缓存结果

  • 这个模式通常叫做记忆化(memorization)或延迟计算(lazy evaluation)

如何让struct持有闭包

  • struct的定义需要知道所有字段的类型

  • 需要指明闭包的类型

  • 每个闭包实例都有自己唯一的匿名类型,即使两个闭包签名完全一样

  • 所以需要使用:泛型和Trait Bound

Fn Trait

  • Fn Trait由标准库提供

  • 所有的闭包都至少实现了以下trait之一:

  • Fn

  • FnMut

  • FnOnce

使用缓存器(Cacher)实现的限制

  1. Cacher实例假定针对不同的参数arg,value方法总会得到同样的值。

  1. 例子

#[cfg(test)]
mod tests {

    #[test]
    fn call_with_different_value() {
        let mut c = super::Cacher::new(|a| a);
        let v1 = c.value(1);
        let v2 = c.value(2);

        assert_eq!(v2, 2);
    }
}
cargo test
  1. 可以使用HashMap代替单个值:

  1. key:arg参数

  1. value:执行闭包的结果

  1. 只能接收一个u32类型的参数和u32类型的返回值

使用闭包捕获上下文

闭包可以捕获他们所在的环境

  • 闭包可以访问定义它的作用域的变量,而普通函数则不能

  • 会产生内存开销

fn main() {
    let x = 4;

    let equal_to_x = |z| z == x;

    let y = 4;

    assert_eq!(equal_to_x(y), true);
}

闭包从所在环境捕获值的方式

  • 与函数获得参数的三种方式一样:

  1. 取得所有权:FnOnce

  1. 可变借用:FnMut

  1. 不可变借用:Fn

  • 创建闭包时,通过闭包对环境值得使用,Rust推断出具体使用哪个trait:

  • 所有的闭包都实现了FnOnce

  • 没有移动捕获变量的实现了FnMut

  • 无需可变访问捕获变量的闭包实现了Fn

Move关键字

  • 在参数列表前使用move关键字,可以强制闭包获取它所使用的环境值得所有权

  • 当将闭包传递给新线程以移动数据使其归新线程所有时,此技术最为有用

  • 例子:(会报错)

fn main() {
    let x = vec![1, 2, 3];

    let equal_to_x = move |z| z == x;

    println!("can't use x here: {:?}", x);

    let y = vec![1, 2, 3];

    assert_eq!(equal_to_x(y), true);
}

最佳实践

  • 当指定Fn trait bound之一时, 首先用Fn,基于闭包体里的情况,如果需要FnOnce或FnMut,编译器会再告诉你。

迭代器(iterators)

什么是迭代器

  • 迭代器模式:对一系列项执行某些任务

  • 迭代器负责:

  • 遍历每个项

  • 确定序列(遍历)何时完成

  • Rust的迭代器:

  • 懒惰的:除非调用消费迭代器的方法,否则迭代器本身没有任何效果

  • 例子:

fn main() {
    let v1 = vec![1, 2, 3];
    let v1_iter = v1.iter();

    for val in v1_iter {
        println!("Got: {}", val);
    }
}

Iterator trait 和next方法

Iterator trait

  • 所有迭代器都实现了Iterator trait

  • Iterator trait定义于标准库

  • Iterator trait仅要求实现一个方法:next

  • next:

  • 每次返回迭代器中的一项

  • 返回结果包裹在Some里

  • 迭代结束,返回None

  • 可直接在迭代器中调用next方法

  • 例子:

fn main() {
    let v1 = vec![1, 2, 3];
    let v1_iter = v1.iter();
    
    // for循环是已经取得了v1_iter的所有权,所以是可变的
    for val in v1_iter {
        println!("Got: {}", val);
    }
}

#[cfg(test)]
mod tests {

    #[test]
    fn iterator_demonstration() {
        let v1 = vec![1, 2, 3];
        let mut v1_iter = v1.iter();
        
        // 这里没有取得所有权,所以要使得其是可变的
        assert_eq!(v1_iter.next(), Some(&1));
        assert_eq!(v1_iter.next(), Some(&2));
        assert_eq!(v1_iter.next(), Some(&3));

    }
}
cargo test

几个迭代方法

  • iter方法:在不可变引用上创建迭代器

  • into_iter方法:创建的迭代器会获得所有权

  • iter_mut方法:迭代可变的引用

消耗迭代器的方法

  • 在标准库中, iterator trait有一些带默认实现的方法

  • 其中有一些方法会调用next方法

  • 实现iterator trait 时必须实现next方法的原因之一

  • 调用next的方法叫做“消耗型适配器”

  • 因为调用他们会把迭代器消耗尽

  • 例如:sum方法(就会耗尽迭代器)

  • 取得迭代器的所有权

  • 通过反复调用next,遍历所有元素

  • 每次迭代,把当前元素加到一个总和里,迭代结束,返回总和

#[cfg(test)]
mod tests {

    #[test]
    fn iterator_sum() {
        let v1 = vec![1, 2, 3];
        let v1_iter = v1.iter();
        let total: i32 = v1_iter.sum();

        // 这里没有取得所有权,所以要使得其是可变的
        assert_eq!(total, 6);
    }
}

产生其他迭代器的方法

  • 定义在iterator trait 上的另外一些方法叫做“迭代器适配器”

  • 把迭代器转换成不同种类的迭代器

  • 可以通过链式调用使用多个迭代器适配器来执行复杂的操作,这种调用可读性较高

  • 例如:map

  • 接收一个闭包,闭包作用于每个元素

  • 产生一个新的迭代器

  • 例子:

#[cfg(test)]
mod tests {

    #[test]
    fn iterator_sum() {
        let v1 = vec![1, 2, 3];

        let v2: Vec<_> = v1.iter().map(|x| x + 1).collect();

        assert_eq!(v2, vec![2, 3, 4]);
    }
}
  • collect方法:消耗型适配器,把结果收集到一个集合类型中。

使用闭包捕获环境

  • filter方法:

  • 接收一个闭包

  • 这个闭包在遍历迭代器的每个元素时,返回bool类型

  • 如果闭包返回true:当前元素将会在包含在filter产生的迭代器中

  • 如果闭包返回false:当前元素不会包含在filter产生的迭代器中

  • 例子:

#[derive(PartialEq, Debug)]
struct Shoe {
    size: u32,
    style: String,
}

fn shoe_in_my_size(shoes: Vec<Shoe>, shoe_size: u32) -> Vec<Shoe> {
    shoes.into_iter().filter(|x| x.size == shoe_size).collect()
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn filter_by_size() {
        let shoes = vec![
            Shoe {
                size: 10,
                style: String::from("sneaker"),
            },
            Shoe {
                size: 13,
                style: String::from("sandal"),
            },
            Shoe {
                size: 10,
                style: String::from("boot"),
            },
        ];

        let in_my_size = shoe_in_my_size(shoes, 10);

        assert_eq!(
            in_my_size,
            vec![
                Shoe {
                    size: 10, 
                    style: String::from("sneaker")
                },
                Shoe {
                    size: 10,
                    style: String::from("boot"),
                },
            ]
        );
    }
}

创建自定义迭代器

使用Iterator trait 来创建自定义迭代器

  • 实现next方法

struct Counter {
    count: u32,
}

impl Counter {
    fn new() -> Counter {
        Counter { count: 0 }
    }
}

impl  Iterator for Counter {
    type Item = u32;

    fn next(&mut self) -> Option<Self::Item> {
        if self.count < 5 {
            self.count += 1;
            Some(self.count)
        } else {
            None
        }
    }

}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn calling_next_directly() {
        let mut counter = Counter::new();

        assert_eq!(counter.next(), Some(1));
        assert_eq!(counter.next(), Some(2));
        assert_eq!(counter.next(), Some(3));
        assert_eq!(counter.next(), Some(4));
        assert_eq!(counter.next(), Some(5));
        assert_eq!(counter.next(), None);

    }
}
  • 例子2:

struct Counter {
    count: u32,
}

impl Counter {
    fn new() -> Counter {
        Counter { count: 0 }
    }
}

impl  Iterator for Counter {
    type Item = u32;

    fn next(&mut self) -> Option<Self::Item> {
        if self.count < 5 {
            self.count += 1;
            Some(self.count)
        } else {
            None
        }
    }

}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn calling_next_directly() {
        let mut counter = Counter::new();

        assert_eq!(counter.next(), Some(1));
        assert_eq!(counter.next(), Some(2));
        assert_eq!(counter.next(), Some(3));
        assert_eq!(counter.next(), Some(4));
        assert_eq!(counter.next(), Some(5));
        assert_eq!(counter.next(), None);

    }

    #[test]
    fn using_other_iterator_trait_methods() {
        let sum: u32 = Counter::new()
            .zip(Counter::new().skip(1))
            .map(|(a, b)| a * b)
            .filter(|x| x % 3 == 0)
            .sum();

        assert_eq!(18, sum);
    }
}

优化改善上一天的实例项目

main.rs

use minigrep::Config;
use std::env;
// collect
use std::process;

fn main() {

    // env::args_os()  // OsString
    // println!("{:?}", args);

    let config = Config::new(env::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);
    }
}

lib.rs

use std::error::Error;
// collect
use std::fs;
use std::env;


// 读取文件
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);
    }
    // 打印内容
    // println!("\nWith text:\n{}", contents);
    Ok(())
}

pub struct Config {
    pub query: String,
    pub filename: String,
    pub case_sensitive: bool,
}

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 = match args.next() {
            Some(arg) => arg,
            None => return Err("Didn't get a query string"),
        };
        let filename = match args.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 })
    }
}

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()
        .filter(|line| line.contains(query))
        .collect()
}

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.lines() {
    //     if line.to_lowercase().contains(&query) {
    //         results.push(line);
    //     }
    // }
    //
    // results
    //
    contents.lines()
        .filter(|line| line.to_lowercase().contains(query))
        .collect()
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn case_sensitive() {
        let query = "duct";
        let contents = "\
        Rust:\n \
        safe, fast, productive.\n \
        Pick three.\n\
        Duct tape.";

        assert_eq!(vec![" safe, fast, productive."],
        search(query, contents))
    }

    #[test]
    fn case_insensitive() {
        let query = "rUsT";
        let contents = "\
        Rust:\n\
        safe, fast, productive.\n\
        Pick three.\n\
        Trust me.";

        assert_eq!(
            vec!["Rust:", "Trust me."],
            search_case_insensitive(query, contents)
        )
    }
}

讨论闭包和迭代器的运行时性能

  • 迭代器的版本更快一点

零开销抽象 Zero-Cost Abstraction

  • 使用抽象时不会引入额外的运行时开销

总结

零开销抽象,学会迭代器的使用可以在编程道路上走的更快点。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值