闭包
- 闭包:可以捕获其所在环境的匿名函数
- 是匿名函数
- 保存为变量、作为参数
- 可在一个地方创建闭包、然后在另一个上下文中调用闭包来完成运算
- 可从其定义的作用域捕获值
- 例子 - 生成自定义运动计划的程序
- 算法的逻辑并不是重点,重点是算法中的计算过程需要几秒钟时间。
- 目标:不让用户发生不必要的等待
- 仅在必要时调用该算法
- 只调用一次
#[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
- struct的定义需要知道所有字段的类型
- fn trait
- fn traits 由标准库提供
- 所有的闭包都至少实现了以下trait之一
- Fn
- FnMut
- FnOnce
- 创建一个struct,它持有闭包及其调用结果
- 使用缓存器(cacher)实现的限制
- cacher 实例假定针对不同的参数arg,value方法总会得到同样的值
- 可以使用hashmap代替单个值
- key: arg参数
- value: 执行闭包的结果
- 只能接收一个u32类型的参数和u32类型的返回值
- 可以使用hashmap代替单个值
- cacher 实例假定针对不同的参数arg,value方法总会得到同样的值
- 使用闭包来捕获上下文
- 闭包可以访问定义它的作用域内的变量,而普通函数则不能
- 会产生内存开销
- 闭包从所在环境捕获值的方式
- 与函数获得参数的三种方式一样
- 取得所有权: FnOnce
- 可变借用: FnMut
- 不可变借用: Fn
- 创建闭包时,通过闭包对环境值的使用,Rust推断出具体使用哪个trait
- 所有的闭包都实现了fnonce
- 没有移动捕获变量的实现了fnmut
- 无需可变访问捕获变量的闭包实现了Fn
- 与函数获得参数的三种方式一样
- move 关键字
- 在参数列表前使用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]); } }
- 定义在iterator trait 上的另外一些方法叫做“迭代器适配器”
- 使用闭包捕获环境
- 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") }, ] ); } }
- filter方法:
- 使用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; }