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 |
|
在 Rust 模式 中排名 #189
每月下载量 375
在 4 个 仓库中使用 (直接使用 2 个)
190KB
5K SLoC
lender
🙂
一个 借款人,也称为 借款迭代器,是一个返回可变借用项的迭代器。具体来说,这意味着通过后续对 next
的调用,项的引用将被无效化。Niko Matsakis 在他的 博客文章 中解释了关于授予和借用特质的一般观点。
标准 Rust 迭代器无法编写的典型示例,但可以通过借款人实现,例如借款人返回 切片或数组的可变、重叠窗口。
但是借款人比这更通用,因为它们可能返回依赖于迭代器中存储的某些可变状态的项。例如,一个借款人可能返回指向文件行的引用,并重复使用内部缓冲区;另外,从一个按字典顺序排序的整数对迭代器开始,借款人可能返回具有相同第一个坐标的对的迭代器,而不进行任何复制;显然,在这些所有情况下,对 next
的任何调用都会使前一次调用返回的引用无效。
此仓库提供了一个借款人特质和相关实用方法库,使用 #84533 和 #25860 实现 Sabrina Jewson 提出的基于高阶特质边界的 借款设计。
与标准迭代器发生的情况类似,除了基本的 Lender
特质外,还有一个 IntoLender
特质,以及如 for_each
这样的方法。
实际上,这个库实现了Lender
的所有方法,除了partition_in_place
和array_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
,这是一个带有 Item
的 io::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。这是 Iterator
的 Item
提供者的等效。
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模拟的不安全代码,但如果您看到任何可以安全化的不安全代码,请告诉我!