#iterator #traits #lending #next #bounds #standard #lend

无 std lender

基于高阶特质边界的 lending-iterator 特质,具有完整的 std::iter::Iterator 功能

12 个版本

0.2.9 2023 年 12 月 30 日
0.2.8 2023 年 12 月 30 日
0.2.1 2023 年 10 月 30 日
0.1.7 2023 年 8 月 15 日
0.0.2 2023 年 5 月 2 日

Rust 模式 中排名 #189

Download history 108/week @ 2024-04-19 102/week @ 2024-04-26 163/week @ 2024-05-03 74/week @ 2024-05-10 103/week @ 2024-05-17 123/week @ 2024-05-24 126/week @ 2024-05-31 65/week @ 2024-06-07 126/week @ 2024-06-14 111/week @ 2024-06-21 54/week @ 2024-06-28 43/week @ 2024-07-05 53/week @ 2024-07-12 96/week @ 2024-07-19 160/week @ 2024-07-26 58/week @ 2024-08-02

每月下载量 375
4 仓库中使用 (直接使用 2 个)

Apache-2.0 OR LGPL-2.1-or-later OR MIT

190KB
5K SLoC

lender 🙂

downloads dependents miri license

一个 借款人,也称为 借款迭代器,是一个返回可变借用项的迭代器。具体来说,这意味着通过后续对 next 的调用,项的引用将被无效化。Niko Matsakis 在他的 博客文章 中解释了关于授予和借用特质的一般观点。

标准 Rust 迭代器无法编写的典型示例,但可以通过借款人实现,例如借款人返回 切片或数组的可变、重叠窗口

但是借款人比这更通用,因为它们可能返回依赖于迭代器中存储的某些可变状态的项。例如,一个借款人可能返回指向文件行的引用,并重复使用内部缓冲区;另外,从一个按字典顺序排序的整数对迭代器开始,借款人可能返回具有相同第一个坐标的对的迭代器,而不进行任何复制;显然,在这些所有情况下,对 next 的任何调用都会使前一次调用返回的引用无效。

此仓库提供了一个借款人特质和相关实用方法库,使用 #84533#25860 实现 Sabrina Jewson 提出的基于高阶特质边界的 借款设计

与标准迭代器发生的情况类似,除了基本的 Lender 特质外,还有一个 IntoLender 特质,以及如 for_each 这样的方法。

实际上,这个库实现了Lender的所有方法,除了partition_in_placearray_chunks(后者由chunky替换),大多数方法都提供了与等价的Iterator方法相同的功能。

行为上的显著差异包括next_chunk返回一个借出者而不是数组,以及某些闭包需要使用hrc!hrc_mut!hrc_once!(高阶闭包)宏,这些宏为closure_lifetime_binder特性提供了一个稳定的替代方案。

将借出者转换为迭代器,可以使用cloned(当借出是Clone类型时),copied(当借出是Copy类型时),owned(当借出是ToOwned类型时),或者iter(当借出者已经满足Iterator的限制时)。

使用方法

Rust中对实现IntoIterator的类型进行迭代的语法在借出者中不适用。迭代借出者的惯用方法是使用while let循环,如下所示

while let Some(item) = lender.next() {
    // Do something with item
}

请注意,等号后面的表达式不能是返回借出者的方法调用,否则你会无限期地迭代第一个元素。

为了简化使用,我们提供了一个类似函数的过程宏for_!,它使得可以使用与实现IntoLender的类型类似的for语法

for_!(item in into_lender {
    // Do something with item
});

最后,你可以使用接受闭包作为参数的for_each方法,但管理闭包中的生命周期可能会很有挑战性

lender.for_each{
    hrc_mut!(for<'lend> |item: &'lend mut TYPE| {
        // do something with item of type TYPE 
    })
};

注意事项

在继续使用此库之前,你应该考虑使用更成熟的库,如lending-iterator,然而,它并不直接使用更高阶的特质边界,而是通过宏模拟它们。

此外,如果你的未来会有dyn Lender特质对象,这个库肯定不会工作。这个库不是为任何生产代码而设计的,所以请自行承担风险(文档除外!不要忽视不安全的转换!)。

最后,请注意,作为一般规则,如果你能避免使用借出者,你应该这样做。你应该听从Polonius的建议:“既不要成为借方,也不要成为贷方”。

示例

让我们使用可变窗口来计算斐波那契数

use ::lender::prelude::*;
use lender_derive::for_;

// Fibonacci sequence
let mut data = vec![0u32; 3 * 3];
data[1] = 1;

// Fibonacci sequence, most ergonomic usage: for_! procedural macro.
for_!(w in data.array_windows_mut::<3>() {
   w[2] = w[0] + w[1];
});
assert_eq!(data, [0, 1, 1, 2, 3, 5, 8, 13, 21]);

// You can use decostructing assignments with for_!.
for_!([a, b, c] in data.array_windows_mut::<3>() {
   *c = *a + *b;
});
assert_eq!(data, [0, 1, 1, 2, 3, 5, 8, 13, 21]);

// Fibonacci sequence, explicit while let loop: you MUST assign the lender to a variable.
let mut windows = data.array_windows_mut::<3>();
while let Some(w) = windows.next() {
   w[2] = w[0] + w[1];
}
assert_eq!(data, [0, 1, 1, 2, 3, 5, 8, 13, 21]);

// Fibonacci sequence, for_each with hrc_mut!
data.array_windows_mut::<3>()
    .for_each(hrc_mut!(for<'lend> |w: &'lend mut [u32; 3]| {
         w[2] = w[0] + w[1]
    }));
assert_eq!(data, [0, 1, 1, 2, 3, 5, 8, 13, 21]);

这是一个相当牵强的例子,但它展示了如何使用借出者来原地修改切片。

那么,让我们来看一个稍微有趣一点的例子,LinesStr,这是一个带有 Itemio::Lines,这里的 Item&str 而不是 String。这是一个从迭代器本身借用的好例子。

use std::io;
use ::lender::prelude::*;

struct LinesStr<B> {
    buf: B,
    line: String,
}
impl<'lend, B: io::BufRead> Lending<'lend> for LinesStr<B> {
    type Lend = io::Result<&'lend str>;
}
impl<B: io::BufRead> Lender for LinesStr<B> {
    fn next<'lend>(&'lend mut self) -> Option<io::Result<&'lend str>> {
        self.line.clear();
        match self.buf.read_line(&mut self.line) {
            Err(e) => return Some(Err(e)),
            Ok(0) => return None,
            Ok(_nread) => (),
        };
        if self.line.ends_with('\n') {
            self.line.pop();
            if self.line.ends_with('\r') {
                self.line.pop();
            }
        }
        Some(Ok(&self.line))
    }
}

let buf = io::BufReader::with_capacity(10, "Hello\nWorld\n".as_bytes());
let mut lines = LinesStr { buf, line: String::new() };
assert_eq!(lines.next().unwrap().unwrap(), "Hello");
assert_eq!(lines.next().unwrap().unwrap(), "World");

实现出借者

要实现 Lender,首先你需要为你的类型实现 Lending trait。这是 IteratorItem 提供者的等效。

use ::lender::prelude::*;
struct StrRef<'a>(&'a str);
impl<'this, 'lend> Lending<'lend> for StrRef<'this> {
    type Lend = &'lend str;
}

生命周期参数 'lend 描述了 Lend 的生命周期。它通过在底层使用默认泛型 &'lend Self 来工作,这会诱导一个隐式的引用生命周期约束 'lend: 'this,这对于使用与 Lend 一起使用的高阶特征约束是必要的。

接下来,你需要为你的类型实现 Lender trait,这是 Iterator 的出借等效。

use ::lender::prelude::*;
struct StrRef<'a>(&'a str);
impl<'this, 'lend> Lending<'lend> for StrRef<'this> {
    type Lend = &'lend str;
}
impl<'this> Lender for StrRef<'this> {
    fn next<'lend>(&'lend mut self) -> Option<&'lend str> {
        Some(self.0)
    }
}

Lend 类型别名可以用来避免重复指定出借的类型;结合生命周期省略,可以使你的实现更简洁、更少出错。

use ::lender::prelude::*;
struct StrRef<'a>(&'a str);
impl<'this, 'lend> Lending<'lend> for StrRef<'this> {
    type Lend = &'lend str;
}
impl<'this> Lender for StrRef<'this> {
    fn next(&mut self) -> Option<Lend<'_, Self>> {
        Some(self.0)
    }
}

类型推断问题

由于涉及到的复杂类型依赖和高阶特征约束,当前的 Rust 编译器并不能总是推断出正确的出借者及其返回项的类型。通常,当编写接受 Lender 的方法并使用 type 限制返回项类型时,将会正常工作,如下所示:

use lender::*;

struct MockLender {}

impl<'lend> Lending<'lend> for MockLender {
    type Lend = &'lend str;
}

impl Lender for MockLender {
    fn next(&mut self) -> Option<Lend<'_, Self>> {
        None
    }
}

fn read_lender<L>(lender: L)
where
    L: Lender + for<'lend> Lending<'lend, Lend = &'lend str>,
{}

fn test_mock_lender(m: MockLender) {
    read_lender(m);
}

然而,以下使用特征约束限制返回项的代码,在 Rust 1.74.1 中无法编译。

use lender::*;

struct MockLender {}

impl<'lend> Lending<'lend> for MockLender {
    type Lend = &'lend str;
}

impl Lender for MockLender {
    fn next(&mut self) -> Option<Lend<'_, Self>> {
        None
    }
}

fn read_lender<L>(lender: L)
where
    L: Lender,
    for<'lend> Lend<'lend, L>: AsRef<str>,
{}

fn test_mock_lender(m: MockLender) {
    read_lender(m);
}

解决方案是使用显式类型注解。

use lender::*;

struct MockLender {}

impl<'lend> Lending<'lend> for MockLender {
    type Lend = &'lend str;
}

impl Lender for MockLender {
    fn next(&mut self) -> Option<Lend<'_, Self>> {
        None
    }
}

fn read_lender<L>(lender: L)
where
    L: Lender,
    for<'lend> Lend<'lend, L>: AsRef<str>,
{}

fn test_mock_lender(m: MockLender) {
    read_lender::<MockLender>(m);
}

为什么不使用 GATs(通用关联类型)?

通用关联类型(GATs)是在 正好考虑到出借迭代器作为使用场景的情况下引入的。使用 GATs,可以轻松地定义一个出借特征,如下所示:

pub trait Lender {
    type Lend<'lend>: where Self: 'lend;
    fn next(&mut self) -> Option<Self::Lend<'_>>;
}

这看起来很美好,你甚至可以围绕它编写一个完整的库。但是,当尝试指定出借类型的特征约束时,你会遇到障碍,这只能使用 高阶特征约束 完成。

pub trait Lender {
    type Lend<'lend>: where Self: 'lend;
    fn next(&mut self) -> Option<Self::Lend<'_>>;
}

fn read_lender<L: Lender>(lender: L) 
    where for<'lend> L::Lend<'lend>: AsRef<str> {}

再次,这段代码可以无问题编译,但是当尝试使用 read_lender 与实现 Lender 的类型一起使用时,由于 where 子句指定该特征约束必须对所有生命周期成立,这意味着它必须对 'static 有效,并且由于出借者必须比出借项存活时间更长,因此出借者也必须是 'static。因此,直到有某种语法可以限制出现在高阶特征约束中的生命周期变量,基于 GAT 的出借迭代器在实际上几乎没有什么实际用途。

资源

请查看以下帮助我及许多人学习 Rust 和出借迭代器问题的优秀资源。感谢大家!

  • Sabrina Jewson 的博客,她关于为什么生命周期 GATs(尚未)是此问题的解决方案的精彩博文,我强烈推荐阅读。
  • Rust 用户论坛 上,有很多人帮助我更好地理解借用检查器和HRTBs,并对我们这些渴望学习Rust的rustaceans表现出极大的耐心。
  • Daniel Henry-Mantilla 因为其编写的 lending-iterator 以及许多其他优秀的crate,并分享他们的杰出工作。
  • 所有为Rust做出贡献,使其成为如此出色的语言和迭代器库的人。

小心!不安全 & 转换类型!

许多借出模式需要polonius模拟的不安全代码,但如果您看到任何可以安全化的不安全代码,请告诉我!

依赖项