#iterator #lending #traits #bounds #lending-iterator #mutable #overlapping

hrtb-lending-iterator

基于高秩特质界限(HRTBs)的借出迭代器特质

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 数据结构

Apache-2.0 OR LGPL-2.1-or-later

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);
    }
}

与标准迭代器交互

库提供了几个方法,使得可以从标准迭代器的世界移动到借用迭代器的世界,反之亦然。

类型推断问题

由于涉及的类型依赖和高阶特质边界复杂,当前的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);
}

无运行时依赖