Rust Lang Book Ch.13 Iterators, Closures
Closures
Closure是匿名函数,并且可以存下来。此外,Closure会获取创建时的作用域中的变量。
fn generate_workout(intensity: u32, random_number: u32) { let expensive_closure = |num| { //closure,以num为参数,返回num 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) ); } } }
编译器会对Closure做一定程度的参数类型推理。
fn add_one_v1 (x: u32) -> u32 { x + 1 } let add_one_v2 = |x: u32| -> u32 { x + 1 }; let add_one_v3 = |x| { x + 1 }; let add_one_v4 = |x| x + 1 ;
不过,一个Closure只能对应一套参数-返回值类型,编译器推理该Closure对应的类型之后,就会固定下来。
let example_closure = |x| x; let s = example_closure(String::from("hello")); let n = example_closure(5); ^ | expected struct `std::string::String`, found integer help: try using a conversion method: `5.to_string()`
Closure至少实现了Fn, FnMut和FnOnce三种traits之中的一个。如果不需要创建Closure上下文中的变量,能传Closure的地方也能传Function
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); v } } } }
FnOnce直接consume(也即拿走ownership)它所需要的创建上下文中的变量,当然要注意由于ownership只能拿一次,所以FnOnce也只能调用一次。
Fn则只读地从创建时的上下文中borrow values。
FnMut从创建时的上下文中borrow values,同时还能改这些变量。
fn main() {
let x = 4;
let equal_to_x = |z| z == x;//capture x
let y = 4;
assert!(equal_to_x(y));
}
Rust编译器会根据Closure内容决定该Closure实现哪些traits。所有Closure都实现了FnOnce。对于不会发生Move,也就是不会发生Ownership改动的Closure,编译器会令其实现FnMut。对哪些不会更改captured variables的值的Closures,编译器会令其实现Fn。
如果一定要closure拿走它captured的variables的ownership,可以使用move关键字。
fn main() {
let x = vec![1, 2, 3];
let equal_to_x = move |z| z == x;
let y = vec![1, 2, 3];
assert!(equal_to_x(y));
}
一般来说可以先试试用Fn来当trait bound,如果需要用FnMut或者FnOnce,编译器报错再改。
Iterator
Iterator是懒计算的,可以用来遍历集合。
let v1 = vec![1, 2, 3]; let v1_iter = v1.iter(); for val in v1_iter {//这里不需要声明v1_iter为mutable,因为for循环自动帮我们标了 println!("Got: {}", val); }
所有的迭代器都实现了一个特性Iterator,Iterator实现了方法next(),返回一个包含子元素的Option,当迭代完成后,返回值为None,否则为Some(子元素)。
pub trait Iterator {
type Item;
fn next(&mut self) -> Option<Self::Item>;
// methods with default implementations elided
}
#[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)); assert_eq!(v1_iter.next(), None); }
如果需要iterator接管被遍历的集合的ownership并返回带有ownership的值,可以使用into_iter。
如果需要产生mutable reference,我们可以调用iter_mut。
因为Iterator是懒加载的,所以可以把多个映射结合在一起形成计算链,最后再调用Consuming Adaptor得到最终结果。
let v2: Vec<_> = v1.iter().map(|x| x + 1).collect()//这里map从v1.iter()生成了新的iterator
shoes.into_iter().filter(|s| s.size == shoe_size).collect()
Consuming Adaptors
调用iterator的next()方法的函数被称为consuming adaptor
#[test] fn iterator_sum() { let v1 = vec![1, 2, 3]; let v1_iter = v1.iter(); let total: i32 = v1_iter.sum();//sum是Consuming adaptor assert_eq!(total, 6); }
Customed Iterator
只要为自己的struct实现Iterator trait即可
struct Counter { count: u32, } impl Counter { fn new() -> Counter { Counter { count: 0 } } } fn main() {}
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
}
}
}
#[test] fn using_other_iterator_trait_methods() { let sum: u32 = Counter::new() .zip(Counter::new().skip(1))//这里只会生成(1, 2), ...(4,5),这4对,(5, None)不会生成 .map(|(a, b)| a * b) .filter(|x| x % 3 == 0) .sum(); assert_eq!(18, sum); }
用Iterator也可以读取std::env::args:
impl Config { pub fn new(mut args: env::Args) -> 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 case_sensitive = env::var("CASE_INSENSITIVE").is_err(); Ok(Config { query, filename, case_sensitive, }) } }
Loops vs. Iterators 性能比较
书中用for循环写了一个过滤文件语句找是否存在关键词的程序,又用iterator写了一版,关键代码分别是:
for循环
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 }
iterator:
pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> { contents .lines() .filter(|line| line.contains(query)) .collect() }
用柯南道尔的福尔摩斯作为测试,二者性能差不多,甚至iterator更甚一筹。书中认为这是因为Iterator是zero-cost abstractions,即无代价的抽象。这些iterator实际编译的底端代码已经性能很优秀,所以不会付出更多的运行代价。而且使用iteration,在编译器已知循环次数很少的时候,还会自动启动unrolling,即直接生成重复性的代码,以节约loop控制时间。