生命周期
让我们尝试通过为 &TicketStore
添加 IntoIterator
的实现来完成之前的练习,以便在 for
循环中获得最大的便利性。
首先,我们填写实现中最“明显”的部分:
#![allow(unused)] fn main() { impl IntoIterator for &TicketStore { type Item = &Ticket; type IntoIter = // 这里应该填什么类型? fn into_iter(self) -> Self::IntoIter { self.tickets.iter() } } }
type IntoIter
应该设置为什么类型呢?直观地,它应该是由 self.tickets.iter()
返回的类型,即 Vec::iter()
返回的类型。如果你查阅标准库文档,你会发现 Vec::iter()
返回了一个 std::slice::Iter
。Iter
的定义是:
#![allow(unused)] fn main() { pub struct Iter<'a, T> { /* 字段省略 */ } }
'a
是一个 生命周期参数。
生命周期参数
生命周期是 Rust 编译器用来追踪引用(无论是可变还是不可变)有效时间的 标签。
引用的生命周期受到它所指向值的作用域限制。Rust 总是在编译时确保引用不会在其指向的值被丢弃后使用,以避免悬挂指针和使用已释放内存的错误。
这听起来应该很熟悉:在讨论所有权和借用时,我们已经看到过这些概念的应用。生命周期只是给特定引用的有效时间 命名 的方式。
当存在多个引用并且需要澄清它们彼此之间的 关联关系 时,命名变得重要。让我们看看 Vec::iter()
的签名:
#![allow(unused)] fn main() { impl <T> Vec<T> { // 稍作简化 pub fn iter<'a>(&'a self) -> Iter<'a, T> { // [...] } } }
Vec::iter()
对一个名为 'a
的生命周期参数是泛型的。
'a
用来 绑定 Vec
的生命周期和由 iter()
返回的 Iter
的生命周期。通俗地说:由 iter()
返回的 Iter
不能超过创建它的 Vec
引用(&self
)的生命周期。
这一点很重要,因为如前所述,Vec::iter
返回的是 对 Vec
元素的引用。如果 Vec
被丢弃,迭代器返回的引用将无效。Rust 必须确保这种情况不会发生,而生命周期就是它用来实施这一规则的工具。
生命周期省略
Rust 有一套称为 生命周期省略规则 的规则,在很多情况下允许你省略显式的生命周期注解。例如,Vec::iter
在 std
源代码中的定义是这样的:
#![allow(unused)] fn main() { impl <T> Vec<T> { pub fn iter(&self) -> Iter<'_, T> { // [...] } } }
Vec::iter()
的签名中没有显式的生命周期参数。省略规则意味着 iter()
返回的 Iter
的生命周期与 &self
引用的生命周期相关联。你可以把 '_
当作 占位符 来理解,代表 &self
引用的生命周期。
有关生命周期省略的官方文档链接,请参阅 参考资料 部分。在大多数情况下,你可以依赖编译器告诉你何时需要添加显式的生命周期注解。