十一、Rust 中的函数式编程

567 阅读4分钟

十一、Rust 中的函数式编程

1. 闭包

  • 闭包:可以保存进变量或作为参数传递给其他函数的匿名函数

    • 注意:Rust 中的闭包和 Javascript 中的闭包是 不同 的概念!
  • 特性

    • 闭包是一种特殊的函数,虽然定义形式和函数不同
    • 可以在一个地方创建闭包,然后在不同的上下文 (context) 中执行闭包运算
    • 闭包允许捕获调用者作用域中的值
    • 闭包不用于对外暴露接口,而是储存在变量中被使用,不会暴露给外部使用者
    • 通常情况下,闭包的内容很少,并且只在相对应的上下文中
    • 如果尝试调用闭包两次,且两次使用了不同的参数类型,则会得到一个报错,这是不被允许的
  • 环境中的变量捕获的 3 个 trait

    • FnOnce: 闭包必须从环境中获取变量的所有权,在定义这个闭包的时候就会把变量移动进闭包
      • 这样的闭包只能调用一次,因为只能从它所在的环境捕获一次变量
      • 可以通过在参数列表前使用 move 关键字来将外部环境的所有权移动到闭包内
      • 将闭包传递给新线程,同时将数据移动到新线程中时,最为实用
    • FnMut: 闭包可以从环境中获取到可变的借用值
    • Fn: 闭包可以从环境中获取到不可变的借用值
  • 基本用法

    // 来自官方教程的一个示例
    
    fn main() {
        // 可以不加类型注解
        let expensive_closure = |num| {
            println!("calculating slowly...");
            thread::sleep(Duration::from_secs(2));
            num
        };
    
        // 也可以加上类型注解
        let expensive_closure2 = |num: u32| -> u32 {
            println!("calculating slowly...");
            thread::sleep(Duration::from_secs(2));
            num
        };
    
        println!("Today, do {} pushups!", expensive_closure(intensity));
    }
    
    
  • 结构体中的闭包

    // 来自官方教程的一个示例
    
    struct Cacher<T>
    where T: Fn(u32) -> u32
    {
        calculation: T,
        value: Option<u32>,
    }
    
    impl<T> Cacher<T>
    where T: Fn(u32) -> u32
    {
        fn new(calculation: T) -> Cacher<T> {
            Cacher {
                calculation,
                value: None,
            }
        }
    
        fn value(&mut self, arg: u32) -> u32 {
            match self.value {
                Some(v) => v,
                None => {
                    let v = (self.calculation)(arg);
                    self.value = Some(v);
                    return v;
                },
            }
        }
    }
    
    

2. 迭代器

  • 迭代器模式:允许你对一个序列进行某些处理

  • 迭代器作用:负责遍历序列中的每一项,并决定遍历何时结束

  • 特性

    • 惰性 (lazy) 的。在真正使用迭代器之前,即使调用了迭代器方法,也不会有任何效果
    • 迭代器都实现了 Iterator 这个 trait
    • 在迭代器上调用 next 方法,会改变迭代器中用来记录序列位置的状态
  • 获取 iterator

    fn main(){
        let mut vector = vec![1, 2, 3];
    
        vector.iter(); // 获取不可变引用的迭代器
        vector.iter_mut(); // 获取可变引用的迭代器
        vector.into_iter(); // 获取拥有元素所有权的迭代器
    }
    
    
  • 消费 iterator

    fn main(){
        let mut vector = vec![1, 2, 3];
    
        let i1 = vector.iter();
        i1.next(); // 消费迭代器的当前元素
    
        let i2 = vector.iter();
        v2.sum(); // 消费整个迭代器里的元素
    }
    
    
  • 迭代适配器

    • 允许将当前迭代器变成不同类型的迭代器
    • 可以链式调用多个迭代适配器
  • 手动为某个类型实现迭代器

    // 来自官方教程的一个示例
    
    // 一个计数器
    pub struct Counter {
        count: u32,
    }
    
    impl Counter {
        // 创建一个 Counter 实例
        fn new() -> Counter {
            Counter { count: 0 }
        }
    }
    
    // 为 Counter 实现迭代器
    impl Iterator for Counter {
        type Item = u32; // 指定这个 Iterator 返回的的元素类型
    
        // Iterator 要求必须实现的,且返回值必须是 Option<T> 类型的
        fn next(&mut self) -> Option<Self::Item> {
            self.count += 1;
    
            if self.count < 6 {
                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);
        }
    }
    
    
    

3. 关于性能

  • 迭代器作为一个高级的抽象,developer 编写的迭代器代码,会被编译成与手写的底层代码大体一致的性能的代码,因此迭代器并不会造成运行时开销
  • Rust 竭力提供零成本的闭包和迭代器抽象

4. 案例改进

  • 在之前实现的案例中,学习了本章的一些内容之后,就可以进行改进了。下文即为改进后的代码。未提及的代码不需要改动

  • main.rs

    use std::{env, process};
    
    use minigrep::Config;
    
    fn main() {
        // * 第一步:获取用户输入的参数
        // 注意:env::args() 的任何参数包含无效 Unicode 字符时会 panic
        // 扩展:如果要处理无效 Unicode 字符,可以使用 std::env::args_os().collection(),来获取 OsString 的集合
        let conf = Config::new(env::args()).unwrap_or_else(|err| {
            println!("{}", err);
            process::exit(1)
        });
    
        // * 第二步:执行期望的逻辑
        if let Err(e) = minigrep::run(conf) {
            println!("{}", e);
            process::exit(1);
        }
    }
    
    
  • lib.rs

    use std::{env, error::Error, fs};
    
    pub struct Config {
        pub query: String,
        pub filename: String,
        pub case_sensitive: bool,
    }
    
    impl Config {
        pub fn new(mut args: env::Args) -> Result<Self, &'static str> {
            // 取出第一个参数: .exe 程序名称
            // 从之前的方式,改成了 迭代器
            match args.next() {
                Some(arg) => arg,
                None => return Err("系统错误"),
            };
    
            let query = match args.next() {
                Some(arg) => arg,
                None => return Err("请输入想要查找的单词"),
            };
    
            let filename = match args.next() {
                Some(arg) => arg,
                None => return Err("请输入查找的文件"),
            };
    
            // 处理环境变量
            let case_sensitive = env::var("CASE_INSENSITIVE").is_err();
    
            return Ok(Config {
                query,
                filename,
                case_sensitive,
            });
        }
    }
    
    pub fn search<'a>(query: &'a str, file_content: &'a str) -> Vec<&'a str> {
        // 从之前的方式,改成了 闭包+迭代器
        return file_content
            .lines()
            .filter(|line| line.contains(query))
            .collect();
    }
    
    pub fn search_case_insensitive<'a>(query: &'a str, file_content: &'a str) -> Vec<&'a str> {
        let query_lower = query.to_lowercase();
        // 从之前的方式,改成了 闭包+迭代器
        return file_content
            .lines()
            .filter(|line| line.to_lowercase().contains(&query_lower))
            .collect();
    }
    
    /// Result<(), Box<dyn Error>> 意味着函数会返回实现了 Error trait 的类型
    /// dyn <=> dynamic
    pub fn run(conf: Config) -> Result<(), Box<dyn Error>> {
        let file_content = fs::read_to_string(&conf.filename)?;
        let result = if conf.case_sensitive {
            search(&conf.query, &file_content)
        } else {
            search_case_insensitive(&conf.query, &file_content)
        };
    
        if result.len() == 0 {
            println!(
                "文件 {} 中未查询到包含 {} 的行...",
                conf.filename, conf.query
            );
        } else {
            for current_line in result {
                println!("{}", current_line);
            }
        }
    
        return Ok(());
    }
    
    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn case_sensitive() {
            let query = "duct";
            let file_content = "\n
    Rust:
    safe, fast, productive.
    Pick three.";
            assert_eq!(vec!["safe, fast, productive."], search(query, file_content));
        }
    
        #[test]
        fn case_insensitive() {
            let query = "rUsT";
            let file_content = "\n
    Rust:
    safe, fast, productive.
    Pick three.
    Trust me.";
            assert_eq!(
                vec!["Rust:", "Trust me."],
                search_case_insensitive(query, file_content)
            );
        }
    }