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控制时间。

浙公网安备 33010602011771号