闭包(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)实现的限制
Cacher实例假定针对不同的参数arg,value方法总会得到同样的值。
例子
#[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

可以使用HashMap代替单个值:
key:arg参数
value:执行闭包的结果
只能接收一个u32类型的参数和u32类型的返回值
使用闭包捕获上下文
闭包可以捕获他们所在的环境
闭包可以访问定义它的作用域的变量,而普通函数则不能
会产生内存开销
fn main() {
let x = 4;
let equal_to_x = |z| z == x;
let y = 4;
assert_eq!(equal_to_x(y), true);
}
闭包从所在环境捕获值的方式
与函数获得参数的三种方式一样:
取得所有权:FnOnce
可变借用:FnMut
不可变借用: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
使用抽象时不会引入额外的运行时开销
总结
零开销抽象,学会迭代器的使用可以在编程道路上走的更快点。