1 个不稳定版本
0.1.0 | 2024 年 3 月 14 日 |
---|
#624 in Rust 模式
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 不是一个迭代器。我怎么能“映射”它呢?
Defray
为 IntoIterator
实现了 &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实现 以获取示例。