Rust入门失败之Iterator Adapters

有一个月没更新了, 自上次9月底更新后就去旅游了. 旅游回来接着国庆, 10月份想着把《Programming Rust》这本一起看完后再续写这篇Rust入门失败系列(其实就是把做的读书笔记重新摘抄出来, 把书读薄撒), 所以就一直没更新. 当然, 也看完了.

转眼加班一年了, 这一年反而看了20几本书, 比前几年都多. 其中两本Rust, 期间又在某宝上买了印刷版《Rust Hight Performance》和《Hands‑On Microservices with Rust》, 这两本聊一些Rust最佳实践的书, 价格相对便宜. 国内引进的Rust书实在少, 没办法支持这么多超级贵的原版书. 读完了基础知识, 做完了Rust官方练习, 最近在看《Rust Hight Performance》, 一直手痒想写些东西. 所以这个入门失败系列不能保证完美收官. 嗯, 不能立flag.

废话不多说了, 继续聊聊Rust的迭代器.

Iterator Adapter

迭代器适配器是迭代器(Iterator)上一系列使用(consume)其自身Items的适配方法(Adapters), 这些方法返回一个具有新功能的迭代器.

  • 常见的有map, filter等. 这类方法通常返回的是一个实现了Iterator trait的结构体.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    // Iterator
    fn map<B, F>(self, f: F) -> Map<Self, F>
    where
    F: FnMut(Self::Item) -> B
    { ... }

    // Map
    pub struct Map<I, F> { /* fields omitted */ }

    impl<B, I, F> Iterator for Map<I, F>
    where
    F: FnMut(<I as Iterator>::Item) -> B,
    I: Iterator
    { ... }
  • 这类适配器方法通常会接受一个闭包. 根据适配器的用途不同, 闭包的参数也不同. 比如上面的map适配器, 其接受的闭包参数类型是Self::Item, 也就是会把每个元素的所有权转交给闭包再转交给返回的下一个迭代器. 当然, 类似filter的适配方法不会捕获元素的所有权, 因此它们的闭包参数就是Item的引用类型.

    1
    2
    3
    4
    fn filter<P>(self, predicate: P) -> Filter<Self, P>
    where
    P: FnMut(&Self::Item) -> bool,
    { ... }

对于迭代器适配器(Iterator Adapter)有很重要的两点需要记住:

  1. 单纯调用适配器方法并不会执行任何元素上的迭代(cousmue). 直到要求使用最终返回的元素时(例如collect方法), 整个迭代过程才会被执行. 这很好理解, Lazy行为.

  2. 这些迭代器的时候都是零成本抽象(zero-overhead abstraction). 在代码里从调用map, filter这些迭代器适配方法到它们返回的类型都是泛型(generic)的, 且Rust有足够的信息在编译时期通过类型推导生成泛型特化的适配器方法并且把它们的next方法內联(inline). 也就是说在运行时的效率和手写这类适配方法是一样的. 比如下面的调用, 两者在运行上的效率是等价的.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    let text = "   ponies   \n   giraffes\niguanas  \nsquid".to_string();
    // Use Iterator Adapters
    let v: Vec<&str> = text.lines()
    .map(str::trim)
    .filter(|s| *s != "iguanas") // s is a shared reference
    .collect();
    assert_eq!(v, ["ponies", "giraffes", "squid"]);

    // Probably write by hand
    for line in text.lines() {
    let line = line.trim();
    if line != "iguanas" {
    v.push(line);
    }
    }

官方文档有很多适配器方法, 这里就不一一赘述, 参考文档即可.