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

 

posted @ 2020-10-26 20:08  雪溯  阅读(101)  评论(0编辑  收藏  举报