Rust(十三)- 函数式语言特性-迭代器和闭包

155 阅读7分钟

闭包

  • 闭包:可以捕获其所在环境的匿名函数
    • 是匿名函数
    • 保存为变量、作为参数
    • 可在一个地方创建闭包、然后在另一个上下文中调用闭包来完成运算
    • 可从其定义的作用域捕获值
    • 例子 - 生成自定义运动计划的程序
      • 算法的逻辑并不是重点,重点是算法中的计算过程需要几秒钟时间。
      • 目标:不让用户发生不必要的等待
        • 仅在必要时调用该算法
        • 只调用一次
        #[derive(Debug, PartialEq, Copy, Clone)]
          enum ShirtColor {
              Red,
              Blue,
          }
        
          struct Inventory {
              shirts: Vec<ShirtColor>,
          }
        
          impl Inventory {
              fn giveaway(&self, user_preference: Option<ShirtColor>) -> ShirtColor {
                  user_preference.unwrap_or_else(|| self.most_stocked())
              }
        
              fn most_stocked(&self) -> ShirtColor {
                  let mut num_red = 0;
                  let mut num_blue = 0;
        
                  for color in &self.shirts {
                      match color {
                          ShirtColor::Red => num_red += 1,
                          ShirtColor::Blue => num_blue += 1,
                      }
                  }
                  if num_red > num_blue {
                      ShirtColor::Red
                  } else {
                      ShirtColor::Blue
                  }
              }
          }
        
          fn main() {
              let store = Inventory {
                  shirts: vec![ShirtColor::Blue, ShirtColor::Red, ShirtColor::Blue],
              };
        
              let user_pref1 = Some(ShirtColor::Red);
              let giveaway1 = store.giveaway(user_pref1);
              println!(
                  "The user with preference {:?} gets {:?}",
                  user_pref1, giveaway1
              );
        
              let user_pref2 = None;
              let giveaway2 = store.giveaway(user_pref2);
              println!(
                  "The user with preference {:?} gets {:?}",
                  user_pref2, giveaway2
              );
          }
        
    • 闭包类型推断和标注
      • 闭包不要求标参数和返回值的类型
      • 闭包通常很短小,只在狭小的上下文中工作,编译器通常能推断出类型
      • 可以手动添加类型标注
      use std::thread;
         use std::time::Duration;
      
         fn generate_workout(intensity: u32, random_number: u32) {
             let expensive_closure = |num: u32| -> u32 {
                 println!("calculating 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 main() {
             let simulated_user_specified_value = 10;
             let simulated_random_number = 7;
      
             generate_workout(simulated_user_specified_value, simulated_random_number);
         }
      
    • 闭包的类型推断
      • 注意:闭包的定义最终只会为参数/返回值推断出唯一具体的类型
  • 使用泛型参数和fn trait 来存储闭包
    • 创建一个struct,它持有闭包及其调用结果
        impl<T> Option<T> {
              pub fn unwrap_or_else<F>(self, f: F) -> T
              where
                  F: FnOnce() -> T
              {
                  match self {
                      Some(x) => x,
                      None => f(),
                  }
              }
          }
      
      
      • 只会在需要结果时才执行该闭包
      • 可缓存结果
    • 这个模式通常叫做记忆化(memorization)或延迟计算(lazy evaluation)
    • 如何让struct持有闭包
      • struct的定义需要知道所有字段的类型
        • 需要指明闭包的类型
      • 每个闭包实例都有自己唯一的匿名类型,即使两个闭包签名完全一样。
      • 所以需要使用:泛型和trait bound
    • fn trait
      • fn traits 由标准库提供
      • 所有的闭包都至少实现了以下trait之一
        • Fn
        • FnMut
        • FnOnce
  • 使用缓存器(cacher)实现的限制
    • cacher 实例假定针对不同的参数arg,value方法总会得到同样的值
      • 可以使用hashmap代替单个值
        • key: arg参数
        • value: 执行闭包的结果
      • 只能接收一个u32类型的参数和u32类型的返回值
  • 使用闭包来捕获上下文
    • 闭包可以访问定义它的作用域内的变量,而普通函数则不能
    • 会产生内存开销
  • 闭包从所在环境捕获值的方式
    • 与函数获得参数的三种方式一样
      • 取得所有权: FnOnce
      • 可变借用: FnMut
      • 不可变借用: Fn
    • 创建闭包时,通过闭包对环境值的使用,Rust推断出具体使用哪个trait
      • 所有的闭包都实现了fnonce
      • 没有移动捕获变量的实现了fnmut
      • 无需可变访问捕获变量的闭包实现了Fn
  • move 关键字
    • 在参数列表前使用move关键字,可以强制闭包取得它所使用的环境值的所有权
      • 当将闭包传递给新线程以移动数据使其归新线程所有时,此技术最为有用

迭代器

  • 迭代器模式:对一系列项执行某些任务
  • 迭代器负责:
    • 遍历每个项
    • 确定序列(遍历)何时完成
  • Rust 的迭代器
    • 懒惰的:除非调用消费迭代器的方法,否则迭代器本身没有任何效果
  • iterator trait 和 next 方法
    • 所有的迭代器都实现iterator trait
    • iterator trait 定义于标准库,定义大致如下:
    fn main() {
          let v1 = vec![1, 2, 3];
    
          let v1_iter = v1.iter();
    
          for val in v1_iter {
              println!("Got: {}", val);
          }
      }
    
    
    • type item 和 self: item定义了与此应trait关联的类型
    • iterator trait仅要求实现一个方法:next
    • next:
      • 每次返回迭代器的一项
      • 返回结果包裹在some里
      • 迭代结束,返回none
    • 可直接在迭代器上调用next方法
    fn main() {
          let v1 = vec![1, 2, 3];
    
          let v1_iter = v1.iter();
    
          for val in v1_iter {
              println!("Got: {}", val);
          }
      }
    
    
    • 几个迭代方法
      • iter方法:在不可变引用上创建迭代器
      • into_iter:创建的迭代器会获得所有权
      • iter_mut:迭代可变的引用
  • 消耗迭代器的方法
    • 在标准库中,iterator trait 有一些带默认实现的方法
    • 其中有一些方法会调用next方法
      • 实现iterator trait 时必须实现 next 方法的原因之一
    • 调用next的方法叫做“消耗型适配器”
      • 因为调用它们会把迭代器消耗尽
    • 列如: sum方法(就会耗尽迭代器)
      • 取得迭代器的所有权
      • 通过反复调用next,遍历所有元素
      • 每次迭代,把当前元素添加到一个总和里,迭代结束,返回总和
  • 产生其它迭代器的方法
    • 定义在iterator trait 上的另外一些方法叫做“迭代器适配器”
      • 把迭代器转换为不同种类的迭代器
    • 可以通过链式调用使用多个迭代器适配器来执行复杂的操作,这种调用可读性较高
    • 例如:map
      • 接收一个闭包,闭包作用于每个元素
         fn main() {
              let v1: Vec<i32> = vec![1, 2, 3];
      
              let v2: Vec<_> = v1.iter().map(|x| x + 1).collect();
      
              assert_eq!(v2, vec![2, 3, 4]);
          }
      }
      
      
  • 使用闭包捕获环境
    • filter方法:
      • 接受一个闭包
      • 这个闭包在遍历迭代器的每个元素时,返回bool类型
      • 如果闭包返回true: 当前元素将会包含在filter产生的迭代器中
      • 如果闭包返回false: 当前元素将不会包含在filter产生的迭代器中 old
      #[derive(PartialEq, Debug)]
          struct Shoe {
              size: u32,
              style: String,
          }
      
          fn shoes_in_size(shoes: Vec<Shoe>, shoe_size: u32) -> Vec<Shoe> {
              shoes.into_iter().filter(|s| s.size == shoe_size).collect()
          }
      
          #[cfg(test)]
          mod tests {
              use super::*;
      
              #[test]
              fn filters_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 = shoes_in_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方法
  • 改进I/O项目 +
    use std::env;
          use std::error::Error;
          use std::fs;
    
          pub struct Config {
              pub query: String,
              pub filename: String,
              pub ignore_case: 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 ignore_case = env::var("IGNORE_CASE").is_ok();
    
                  Ok(Config {
                      query,
                      filename,
                      ignore_case,
                  })
              }
          }
    
          pub fn run(config: Config) -> Result<(), Box<dyn Error>> {
              let contents = fs::read_to_string(config.filename)?;
    
              let results = if config.ignore_case {
                  search_case_insensitive(&config.query, &contents)
              } else {
                  search(&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 query = query.to_lowercase();
              let mut results = Vec::new();
    
              for line in contents.lines() {
                  if line.to_lowercase().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)
                  );
              }
          }
    
    

new

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

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

      impl Config {
          pub fn new(
              mut args: impl Iterator<Item = String>,
          ) -> Result<Config, &'static str> {
              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 file name"),
              };

              let ignore_case = env::var("IGNORE_CASE").is_ok();

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

      pub fn run(config: Config) -> Result<(), Box<dyn Error>> {
          let contents = fs::read_to_string(config.filename)?;

          let results = if config.ignore_case {
              search_case_insensitive(&config.query, &contents)
          } else {
              search(&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 query = query.to_lowercase();
          let mut results = Vec::new();

          for line in contents.lines() {
              if line.to_lowercase().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)
              );
          }
      }

性能比较 loop vs iterator

  • 一个测试

    • 把一本小说的内容放在一个string里面,搜索"the"
    test bench_search_for  ... bench:  19,620,300 ns/iter (+/- 915,700)
    test bench_search_iter ... bench:  19,234,900 ns/iter (+/- 657,200)
    
    • 迭代器的版本更快一点!
  • 零开销抽象(zero-cost abstraction)

    • 使用抽象时不会引入额外的运行时开销
     let buffer: &mut [i32];
     let coefficients: [i64; 12];
     let qlp_shift: i16;
    
     for i in 12..buffer.len() {
         let prediction = coefficients.iter()
                                      .zip(&buffer[i - 12..i])
                                      .map(|(&c, &s)| c * s as i64)
                                      .sum::<i64>() >> qlp_shift;
         let delta = buffer[i];
         buffer[i] = prediction as i32 + delta;
     }