Iter
好的,让我们试着实现 Iter。这一次,我们不能依靠 List 来提供所有我们想要的功能。我们得自己动手了。我们需要的基本逻辑是保持一个指针,指向下一个要生成的当前节点。因为该节点可能不存在(列表为空或者我们已经进行了迭代) ,所以我们希望该引用是 Option。当我们生成一个元素时,我们要前进到当前节点的下一个节点。
好吧,让我们试试这个:
pub struct Iter<T> {
next: Option<&Node<T>>,
}
impl<T> List<T> {
pub fn iter(&self) -> Iter<T> {
Iter { next: self.head.map(|node| &node) }
}
}
impl<T> Iterator for Iter<T> {
type Item = &T;
fn next(&mut self) -> Option<Self::Item> {
self.next.map(|node| {
self.next = node.next.map(|node| &node);
&node.elem
})
}
}天啊。 生命周期。 我听说过这些东西。 我听说他们是一场噩梦。
让我们尝试一些新的东西: 看到那个错误[ E0106]的东西吗?这是一个编译器错误代码。我们可以要求 rustc 解释这些。使用 --explain 查看:
这个,呃... ... 这并没有真正说清楚(这些文件认为我们现在更了解 Rust)。但是看起来我们应该把 'a 加到我们的结构中?我们来试试。
我开始看到一种模式。。。让我们把这些小家伙加入到我们能做的事情中:
天啊,我们被 Rust 打败了。
也许我们应该真正弄清楚 'a 生命周期到底意味着什么。
生命周期可能会吓跑很多人,因为它们改变了我们从编程开始就知道和喜爱的东西。到目前为止,我们实际上已经成功地避开了生命周期,尽管它们一直在我们的程序中纠缠不清。
在有垃圾收集器的语言中,生命周期是不必要的,因为垃圾收集器可确保一切按需魔术般地存在。 Rust中的大多数数据都是手动管理的,因此数据需要另一种解决方案。C 和 C++为我们提供了一个清晰的示例,如果让人们随意使用指向堆栈上的指针会发生什么:无处不在的无法管理的不安全性。这可以大致分为两类错误:
拿着指向超出范围的东西的指针
拿着一个指针指向某个修改了的东西
生命周期可以99%解决这两个问题,它们都是以完全透明的方式来解决的。
那么什么是生命周期呢?
简而言之,生命周期是程序中某处代码的区域(〜块/作用域)的名称而已。 当引用标记有生命周期时,我们说它在整个区域都有效。 不同的地方必须引用且需要有效时间。整个生命周期系统反过来就是一个约束求解系统,它试图最小化每个引用的区域。 如果成功找到了满足所有约束条件的一组寿命,则程序将编译! 否则,您将返回一条错误消息,提示某些对象寿命不足。
在一个函数体中,你通常不能谈论生命周期,也不想谈论生命周期。编译器拥有完整的信息,可以推断出所有的约束以找到最小的生命周期。然而,在类型和 api 级别上,编译器并不拥有所有的信息,它要求你告诉它不同生命之间的关系,这样它就能知道你在做什么。
原则上,这些生命周期也可以省略,但检查所有的借用将是一个庞大的整个程序分析,将产生令人难以置信的非本地错误。所有借用检查都可以在每个函数体中独立完成,并且所有错误都应该是局部的(或者您的类型具有不正确的签名)。
但我们之前已经在函数签名中编写了引用,这很好! 这是因为在某些情况下很常见,Rust会自动为您选择生命周期。 这就是生命周期省略。
特别是:
那么 fn foo<'a>(&'a A) -> &'a B 是什么意思?实际上,这意味着输入必须至少与输出生命周期一样长。因此,如果您将输出保留很长时间,这将扩大输入必须对其有效的区域。一旦停止使用输出,编译器就会知道输入也会变得无效。
通过这个系统的建立,Rust 可以确保在释放之后没有任何东西被使用,并且存在未完成的引用时没有任何东西被修改。它只是确保所有的约束都能解决!
好吧,那么,Iter。
让我们回到无生命周期状态:
我们只需要在函数和类型签名中添加生存期:
好了,我想这次我们成功了。
(╯°□°)╯︵ ┻━┻
我们修正了生命周期的错误,但是现在我们得到了一些新的类型的错误。
我们想要存储 &Node,但是我们得到了 &Box。好的,这很简单,我们只需要在引用之前取消对 Box 的引用:
(ノಥ益ಥ)ノ ┻━┻
我们忘记了as_ref,所以我们将 box 移到了 map 中,这意味着它将被丢弃,引用将悬空:
😭
添加了 as_ref 我们需要移除间接层:
🎉 🎉 🎉
你可能会想: “哇,&** 这东西真难看” ,没错。通常 Rust 非常擅长隐式地进行这种转换,通过一个叫做 deref 强制的进程,它基本上可以在整个代码中插入 *'s 来进行类型检查。之所以可以这样做,是因为我们有借用检查器来确保我们永远不会弄乱指针!
但是在这个例子中,我们有一个 Option<&T> 而不是 &T,这个闭包有点太复杂了,所以我们需要为它做这个。值得庆幸的是,在我的经验中,这是相当罕见的。
为了完整起见,我们可以使用涡轮鱼语法给出一个不同的提示:
看,map 是一个泛型:
涡轮鱼:::<>,让我们告诉编译器我们认为这些泛型的类型。::<&Node, _> 表示“它应该返回一个 &Node 类型,我不知道/也不在乎其他类型。
反过来让编译器知道 &node 应该应用了deref,所以我们不需要手动应用所有这些*!。
在这种情况下,我不认为这是一个真正的改进,这只是一个几乎不加掩饰来炫耀 deref 和 涡轮鱼 语法的借口。
让我们编写一个测试,以确保我们没有使用它或其他任何东西:
最后,应该注意的是,我们实际上可以在这里省略生命周期:
相当于:
耶,更少的生命周期!
或者,如果您不喜欢“隐藏”某个结构包含生命周期,可以使用 Rust 2018 显式省略生命周期语法: ’ _ :
Last updated
Was this helpful?