7 个版本
0.3.1 | 2023 年 10 月 30 日 |
---|---|
0.3.0 | 2023 年 10 月 23 日 |
0.2.3 | 2023 年 10 月 20 日 |
0.1.0 | 2023 年 10 月 16 日 |
#276 in 数据结构
34KB
476 行
警告
在开发此软件包的途中,我们了解到 Lender,它与本软件包具有完全相同的目标,使用了相同的思想,并且发展得更好,因此我们加入了其开发团队。因此,本软件包不再维护,此文档仅出于历史原因保留。
基于高秩特质界限(HRTBs)的借出迭代器特质
一个 借出迭代器 是一个将可变借用借出给它返回的项的迭代器。特别是,这意味着通过下一次调用 next()
使项的引用失效。
标准 Rust 迭代器无法编写的典型示例,但由借出迭代器覆盖的是返回可变、重叠窗口的切片。
但是借出迭代器比这更通用,因为它们可能返回依赖于迭代器中存储的某些可变状态的项。例如,一个借出迭代器可能返回重用内部缓冲区的文件行引用;此外,从一个按字典顺序排序的整数对迭代器开始,借出迭代器可以返回具有相同第一坐标的整数对迭代器,而不进行任何复制;显然,在这些所有情况下,对 next()
的任何调用都会使前一次调用返回的引用失效。
类似于标准迭代器的情况,除了基本的 LendingIterator
特质外,还有 IntoLendingIterator
特质和方法,如 LendingIterator::map
。我们的目标是拥有一个像标准迭代器一样完整的库,但仍有许多工作要做。
Rust 语法中遍历实现 IntoIterator
接口类型的语法不能扩展到借用迭代器。遍历借用迭代器的惯用方法是使用 while let
循环,例如
while let Some(item) = iter.next() {
// Do something with item
}
请注意,如果你有一个具有 iter
方法的变量 x
,该方法返回一个借用迭代器,你不能使用以下形式 while let Some(item) = x.iter().next()
,因为这样你会永远遍历第一个元素。
为了简化迭代,我们提供了一个宏 for_lend!
,它可以以类似于 for
循环的方式迭代。
示例:重用行缓冲区
以下代码演示了如何实现一个返回文件行并重用行缓冲区的借用迭代器
use hrtb_lending_iterator::*;
use std::fs::File;
use std::io::{BufRead, BufReader};
struct Lines {
reader: BufReader<File>,
buffer: String,
}
impl<'any> LendingIteratorItem<'any> for Lines {
type Type = &'any str;
}
impl LendingIterator for Lines {
fn next(&mut self) -> Option<Item<'_, Self>> {
self.buffer.clear();
if self.reader.read_line(&mut self.buffer).unwrap() == 0 {
return None;
}
Some(&self.buffer)
}
}
fn main() {
let mut iter = Lines {
reader: BufReader::new(File::open("Cargo.toml").unwrap()),
buffer: String::new(),
};
while let Some(line) = iter.next() {
// line is a reference to the buffer
print!("{}", line);
}
}
由于库中包含了许多与 Rust 迭代器类似的方法,你可以使用以下代码仅枚举前最多十行
let mut iter = Lines {
reader: BufReader::new(File::open("Cargo.toml").unwrap()),
buffer: String::new(),
}.take(10);
此外,如果你在任何时候决定你更愿意处理所有权的字符串,你只需将借用迭代器转换成标准迭代器,通过使返回的项目拥有所有权即可
for line in iter.to_owned_item() {
// line is a copy of the buffer
print!("{}", line);
}
这是在返回项的类型实现 ToOwned
接口时才可行的。
示例:重叠窗口
以下代码演示了如何实现一个返回切片重叠窗口的借用迭代器
use hrtb_lending_iterator::*;
struct WindowsMut<'a, T, const WINDOW_SIZE: usize> {
slice: &'a mut [T],
curr_pos: usize,
}
impl<'a, 'any, T, const WINDOW_SIZE: usize> LendingIteratorItem<'any>
for WindowsMut<'a, T, WINDOW_SIZE>
{
type Type = &'any mut [T; WINDOW_SIZE];
}
impl<'a, T, const WINDOW_SIZE: usize> LendingIterator for WindowsMut<'a, T, WINDOW_SIZE> {
fn next(&mut self) -> Option<Item<'_, Self>> {
let window = self
.slice
.get_mut(self.curr_pos..)?
.get_mut(..WINDOW_SIZE)?;
self.curr_pos += 1;
Some(window.try_into().unwrap())
}
}
fn main() {
let mut v = vec![1, 2, 3, 4, 5];
let mut iter = WindowsMut::<'_, _, 3> {
slice: &mut v,
curr_pos: 0,
};
while let Some(window) = iter.next() {
// The window is mutable
window[0] = window[2] - window[1];
println!("{:?}", window);
}
}
实际上,这已经通过扩展特质为你完成了,所以你可以直接使用它
use hrtb_lending_iterator::*;
fn main() {
let mut v = vec![1, 2, 3, 4, 5];
let mut iter = v.windows_mut::<3>();
while let Some(window) = iter.next() {
// The window is mutable
window[0] = window[2] - window[1];
println!("{:?}", window);
}
}
与标准迭代器交互
库提供了几个方法,使得可以从标准迭代器的世界移动到借用迭代器的世界,反之亦然。
-
所有实现
Iterator
接口类型都可以通过调用方法Iterator::into_lend_iter
转换为LendingIterator
,所有实现IntoIterator
接口类型都可以通过调用方法IntoIterator::into_into_lend_iter
转换为IntoLendingIterator
。这是通过特质扩展实现的,但方法也可以作为自由函数from_iter
和from_into_iter
提供。这些转换不涉及分配。 -
如果一个贷款迭代器实际上是一个标准迭代器,因为没有实际借用,可以使用方法
LendingIterator::into_iter
来将其转换为贷款迭代器,同样的,使用IntoLendingIterator::into_into_iter
也可以实现。这些转换不会进行分配,并且是前面两个操作的逆操作。 -
方法
LendingIterator::to_owned_item
将贷款迭代器转换为返回拥有项的标准迭代器。只要返回项的类型实现了ToOwned
,就可以实现这一点。如果ToOwned::to_owned
方法在应用到每个项时进行分配,则会有分配。
类型推断问题
由于涉及的类型依赖和高阶特质边界复杂,当前的Rust编译器无法始终推断出贷款迭代器及其返回项的正确类型。通常,当编写接受 LendingIterator
并使用 类型 限制返回项类型的函数时,将会正常工作,如下所示:
use hrtb_lending_iterator::*;
struct MockLendingIterator {}
impl<'any> LendingIteratorItem<'any> for MockLendingIterator {
type Type = &'any str;
}
impl LendingIterator for MockLendingIterator {
fn next(&mut self) -> Option<Item<'_, Self>> {
None
}
}
fn read_lend_iter<L>(iter: L)
where
L: LendingIterator + for<'any> LendingIteratorItem<'any, Type = &'any str>,
{}
fn test_mock_lend_iter(m: MockLendingIterator) {
read_lend_iter(m);
}
然而,以下代码,它使用特质边界来限制返回项,截至Rust 1.73.0版本无法编译
use hrtb_lending_iterator::*;
struct MockLendingIterator {}
impl<'any> LendingIteratorItem<'any> for MockLendingIterator {
type Type = &'any str;
}
impl LendingIterator for MockLendingIterator {
fn next(&mut self) -> Option<Item<'_, Self>> {
None
}
}
fn read_lend_iter<L>(iter: L)
where
L: LendingIterator,
for<'any> <L as LendingIteratorItem<'any>>::Type: AsRef<str>,
{}
fn test_mock_lend_iter(m: MockLendingIterator) {
read_lend_iter(&m);
}
解决方案是使用显式类型注释
use hrtb_lending_iterator::*;
struct MockLendingIterator {}
impl<'any> LendingIteratorItem<'any> for MockLendingIterator {
type Type = &'any str;
}
impl LendingIterator for MockLendingIterator {
fn next(&mut self) -> Option<Item<'_, Self>> {
None
}
}
fn read_lend_iter<L>(iter: L)
where
L: LendingIterator,
for<'any> <L as LendingIteratorItem<'any>>::Type: AsRef<str>,
{}
fn test_mock_lend_iter(m: MockLendingIterator) {
read_lend_iter::<MockLendingIterator>(m);
}