十一、Rust 中的函数式编程
1. 闭包
-
闭包:可以保存进变量或作为参数传递给其他函数的匿名函数
- 注意:Rust 中的闭包和
Javascript中的闭包是 不同 的概念!
- 注意:Rust 中的闭包和
-
特性
- 闭包是一种特殊的函数,虽然定义形式和函数不同
- 可以在一个地方创建闭包,然后在不同的上下文 (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方法,会改变迭代器中用来记录序列位置的状态
-
获取
iteratorfn main(){ let mut vector = vec![1, 2, 3]; vector.iter(); // 获取不可变引用的迭代器 vector.iter_mut(); // 获取可变引用的迭代器 vector.into_iter(); // 获取拥有元素所有权的迭代器 } -
消费
iteratorfn 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.rsuse 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.rsuse 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) ); } }