1 个不稳定版本

0.1.0 2024 年 3 月 14 日

#624 in Rust 模式

MIT/Apache

26KB
496

磨损

底层的非融合且无耻迭代器,传统的迭代器出门

简介

Rust 迭代器[^0] 有几种类型:融合、非融合,现在还有磨损。这个种类取决于它在 .next() 返回 None 之后的行文。

pub trait Iterator {
    type Item;
    fn next(&mut self) -> Option<Self::Item>;
}

融合

一旦融合迭代器 .next() 返回 None,它将只返回 None。它被称为在第一次 None 时耗尽。这个行为可以通过返回一个 std::iter::Fuse 迭代器的 .fuse() 方法来保证。

非融合

大多数迭代器都是非融合的。它们没有像 Fuse 那样的程序性保证,但生产者和消费者默默地同意,向已经返回 None 的迭代器请求 .next() 是不礼貌的。它预计在第一次 None 时耗尽。

磨损

磨损迭代器喜欢在返回 None 后提供更多的元素。它们可以经济地表示多个序列。然而,它们并不是不知疲倦的野蛮人;磨损迭代器预计在连续返回两个 None 时耗尽。

用法

假设我们有一个表示多个序列的迭代器。例如,SevenIter 是一个非融合迭代器,它表示以下序列:[1, 2][4, 5][7]

use frayed::{Frayed, FrayedTools, FraughtTools};

/// This iterator skips every number divisible by 3 up to seven.
struct SevenIter(u8);

/// SevenIter's .next() returns Some(1), Some(2), None, Some(4), Some(5), None, 
/// Some(7), None, None, and so on.
impl Iterator for SevenIter {
    type Item = u8;
    fn next(&mut self) -> Option<u8> {
        self.0 += 1;
        (self.0 % 3 != 0 && self.0 <= 7).then_some(self.0)
    }
}

/// Mark iterator with `.frayed() or impl the `Frayed` marker trait.
let frayed_iter = SevenIter(0).frayed();
/// Defray the frayed iterator into an iterator of iterators.
for subiter in &frayed_iter.defray() {
    for i in subiter {
        print!("{i} ");
    }
    println!()
}

上面的代码将生成以下输出

1 2 
4 5
7 

扩展

FrayedTools

FrayedTools 扩展了标记为 Frayed 的迭代器。

FraughtTools

FraughtTools 扩展了常规迭代器,但通常要么接受磨损迭代器作为参数,要么返回磨损迭代器。

Q&A

为什么将 FrayedTools 限制为仅适用于 Frayed 迭代器?

因为处理一个概念上代表许多迭代器的迭代器是令人困惑的:有时你想要映射所有元素。有时你想要映射子序列。这样编译器就可以帮助我们了。

例如,如果我们忘记将我们的迭代器标记为“磨损”,我们就不能“解磨损”它。

let frayed_iter = SevenIter(0); // .frayed();
let _ = &frayed_iter.defray(); // Not marked frayed. No `defray()` method.

Defray 不是一个迭代器。我怎么能“映射”它呢?

DefrayIntoIterator 实现了 &Defray。当一个人想要返回一个 Defray 但又想要映射它的输出时,就会出现问题。如果可以使用底层的磨损迭代器完成所需操作,可以使用 defrayed.into_inner() 来检索它,处理它,然后再考虑 .defray()-ing 它。

如果一个人想要处理迭代器而不是底层元素,那么如何最好地做到这一点仍然是一个未解决的问题。也许 Defray 本身可以提供一个 map<F,Y>(self, f: F) -> Map<Defray<Iter<Item=X>>, F, Y> where F: FnMut(Iter<Item=X>) -> Y) 函数。

动机

当手动编写代表多个序列的迭代器实现时,通常很容易编写一个 Iterator<Item = Vec<T>>,即使分配和收集 Vec 并非必要或期望。然而,如果希望尊重相关的 API,例如,SubIter 可以无序消费或丢弃,编写一个 Iterator<Item = SubIter<T>> 需要复杂的机制。

如果相反,写一个“磨损”迭代器 Iterator<Item = T>(其中 None 代表子序列的结束而不是迭代器的结束),这通常要容易得多。可以用一些小心来消费这些迭代器,但它们仍然是非传统的和令人惊讶的。

fn raw_consume_unfused<T: std::fmt::Display>(frayed: impl Iterator<Item = T>) {
    let mut frayed = frayed.peekable();
    loop {
        // Consume the subsequence.
        for i in frayed.by_ref() {
            print!("{} ", i);
        }
        println!();
        // Check for second None that means frayed iterator is exhausted.
        if frayed.peek().is_none() {
            break;
        }
    }
}

这个 crate 的初始动机是让“磨损”迭代器对初学者来说更容易消费。考虑下面的代码

use frayed::*;
fn raw_consume_frayed<T: std::fmt::Display>(frayed: impl Iterator<Item = T> + Frayed) {
    for subiter in &frayed.defray() {
        for i in subiter {
            print!("{} ", i);
        }
    }
}

但如果是生产者保持他们的磨损迭代器在幕后,然后暴露我们习惯的抽象,那会更好。

fn chunks(&self) -> frayed::Defray<Iter> {
    // ...
}

fn main() {
    let obj = ...;
    for subiter in &obj.chunks() {
        for i in subiter {
            print!("{} ", i);
        }
    }
}

[^0]: Rust 迭代器有一个非常简洁的特异。

[^1]:请参考 itertools中的GroupBy实现 以获取示例。

无运行时依赖