1 不使用生命周期注解```rust
//=========== 3.5 Iter version 1: 不使用生命周期 // cargo test —features ver1
[cfg(feature = “ver1”)]
mod version1 { use super::*;
pub struct Iter<T> {
next: Option<&Node<T>>, //ver1: 结构体中涉及引用时,没有生命周期注解,定然出错
}
impl<T> List<T> {
pub fn iter(&self) -> Iter<T> {
// ver1: 定然出错: map()方法要求所有权,不能通过&self获取self.head的所有权:
Iter {
next: self.head.map(|node| &node),
}
}
}
impl<T> Iterator for Iter<T> {
type Item = &T;
// ver1: 定然出错: 关联类型涉及引用时没有生命周期注解
fn next(&mut self) -> Option<Self::Item> {
self.next.map(|node| {
// ver1: 不能通过&mut self取得map()所要求的self.next的所有权
self.next = node.next.map(|node| &node);
&node.elem // ver1: 涉及引用时没有生命周期注解
})
}
}
}
- 结构体字段涉及引用时,一定需要生命周期注解
- 函数签名涉及引用时,除self之外,通常需要生命周期注解,除非是后面论述的省略生命周期注解的情形
<a name="7wGXT"></a>
# 2 涉及引用的地方都使用生命周期```rust
//=========== 3.5 Iter Version 2: 所有引用都使用生命周期注解
// cargo test --features ver2
#[cfg(feature = "ver2")]
mod version2 {
use super::*;
pub struct Iter<'a, T> {
next: Option<&'a Node<T>>,
}
impl<'a, T> List<T> {
pub fn iter(&'a self) -> Iter<'a, T> {
Iter {
// 这里有词法错误: 即使不使用特征 ver2,这里也编译出错:生命周期注解只能用于类型声明、函数签名中。
next: self.head.map(|node| &'a node),
}
}
}
impl<'a, T> Iterator for Iter<'a, T> {
type Item = &'a T;
fn next(&'a mut self) -> Option<Self::Item> {
self.next.map(|node| {
// 这里有词法错误: 即使不使用特征 ver2,这里也编译出错:生命周期注解只能用于类型声明、函数签名中。
self.next = node.next.map(|node| &'a node);
&node.elem
})
}
}
}
- 生命周期注解仅用于类型声明、函数签名等处,用于修饰引用类型;而不能用于取引用的场合。
如果在取引用的地方使用生命周期注解,则词法检查都通不过,根本不会进行到语法检查步骤。
3 生命周期详解
3.1 生命周期是什么
生命周期是程序中一个代码区域(region/block/scope)的名字。
通常不用关注函数体中的生命周期:编译器有完全的信息,可以推导出所有约束关系,为引用找到最小存活区域。
- 在类型声明和API级别,编译器没有完全的信息:程序员需要告知不同生命周期之间的关系,让编译器知道你要做什么。也就是说,仅仅需要在类型声明和函数签名中使用生命周期注解。
- 理论上说,类型声明和API级别的生命周期约束也可以推导出来,但这要求对整个程序进行分析,开销巨大,实际上难以实现。而且,即使实现了,一旦编译出错,错误可能不是局部于一个函数的,难以理解。
- 所以编译器分别对每个函数体进行借用检查,检查出的错误仅仅是局部于单个函数的,容易理解。
前面的代码,在函数签名中使用了引用,但没有提供生命周期注解。这是因为某些情况非常普遍,编译器可以自动处理这些情况下的生命周期。这就是
3.3 生命周期省略
仅有一个输入参数涉及引用,则输出参数的生命周期等于这个输入参数的生命周期。
fn foo(&A) -> &B; // sugar for:
fn foo<'a>(&'a A) -> &'a B;
每个引用类型的参数有自己的生命周期。
fn foo(&A, &B, &C); // sugar for:
fn foo<'a, 'b, 'c>(&'a A, &'b B, &'c C);
如果有一个输入参数为
&self
或者&mut self
,则输出生命周期等于self
的生命周期。这一条仅适用于方法。fn foo(&self, &B, &C) -> &D; // sugar for:
fn foo<'a, 'b, 'c>(&'a self, &'b B, &'c C) -> &'a D;
4 正确使用生命周期注解
4.1 仅在类型声明和函数签名中使用生命周期注解
```rust
//=========== Iter Version 3: 仅在类型声明和函数签名中使用生命周期注解 // cargo test —features ver3
[cfg(feature = “ver3”)]
mod version3 { use super::*;
pub struct Iter<’a, T> {
next: Option<&'a Node<T>>,
}
impl
List { // 这里应该可以省略生命周期注解
pub fn iter<'a>(&'a self) -> Iter<'a, T> {
// 1 List<T>的 head 字段的类型是 Option<Box<Node<T>>>
// 则 self.head 的类型是 &Option<Box<Node<T>>>(因为self的类型是&List<T>)
// 2 Option::map()方法的签名为: pub fn map<U, F>(self, f: F) -> Option<U> where F: FnOnce(T) -> U,
// 则可知node的类型是Box<Node<T>>,&node的类型是&Box<Node<T>>,map()方法的返回类型是Option<&Box<Node<T>>>
// 3 而 Iter::next的类型是 Option<&'a Node<T>>,与map()方法返回类型不匹配
// 4 此外,map()要求所有权,但不能通过引用&'a self获取self.head的所有权,必须使用 self.head.as_ref()转化成 Option<&T>
Iter {
next: self.head.map(|node| &node),
}
}
}
impl<’a, T> Iterator for Iter<’a, T> {
type Item = &'a T;
fn next(&mut self) -> Option<Self::Item> {
self.next.map(|node1| {
// 类似上面: self.next的类型是Option<&'a Node<T>>
// node1的类型是&'a Node<T>
// node1.next的类型是 Option<Box<&'a Node<T>>>
// node2 的类型是Box<&'a Node<T>>,其引用为&Box<&'a Node<T>>
// 这与self.next的Option类型要求的&'a Node<T>类型不匹配
self.next = node1.next.map(|node2| &node2);
&node1.elem
})
}
} } ```
4.2 增加解引用运算符```rust
//=========== Iter Version 4: 仅在类型声明和函数签名中使用生命周期注解,并且添加解引用 // cargo test —features ver4
[cfg(feature = “ver4”)]
mod version4 { use super::*;
pub struct Iter<'a, T> {
next: Option<&'a Node<T>>,
}
impl<T> List<T> {
pub fn iter<'a>(&'a self) -> Iter<'a, T> {
// 这里与 version3 的差别是: 增加了解引用运算符 *
// 注意: &*node 似乎是没有意义的: 解引用 与 引用 运算符的效果抵消了?
// 1 node 的类型是 Box<Node<T>>
// 2 *node 的类型是 Node<T>
// 3 &*node的类型是 &Node<T>,这与Box<Node<T>>是不同的,虽然都是引用类型
// 现在问题是: map() 要求所有权类型,而 self.head 是引用类型,不能移动所有权
Iter {
next: self.head.map(|node| &*node),
}
}
}
impl<'a, T> Iterator for Iter<'a, T> {
type Item = &'a T;
fn next(&mut self) -> Option<Self::Item> {
self.next.map(|node1| {
// 这里与 version4 的差别是: 增加了解引用运算符 *
// 注意: &*node2 似乎是没有意义的: 解引用 与 引用 运算符的效果抵消了?
// 1 node2 的类型是 Box<Node<T>>
// 2 *node2 的类型是 Node<T>
// 3 &*node2的类型是 &Node<T>,这与Box<Node<T>>是不同的,虽然都是引用类型
// 现在问题是: node1 类型是 &Node<T>,进而 node1.next 是引用类型,而 map() 要求所有权类型
self.next = node1.next.map(|node2| &*node2);
&node1.elem
})
}
}
}
<a name="H1okJ"></a>
## 4.3 使用两级解引用运算符和as_ref()```rust
//=========== Iter Version 5: 仅在类型声明和函数签名中使用生命周期注解,并且添加两级解引用,使用as_ref()
// cargo test --features ver5
#[cfg(feature = "ver5")]
mod version5 {
use super::*;
pub struct Iter<'a, T> {
next: Option<&'a Node<T>>,
}
impl<T> List<T> {
pub fn iter<'a>(&'a self) -> Iter<'a, T> {
// 这里与 version4 的差别是: (1) 增加了 as_ref() (2) 增加了一级解引用
// type Link<T> = Option<Box<Node<T>>>;
// 1 self.head 类型是 Link<T>,即 Option<Box<Node<T>>>,被包装的是所有权类型
// 2 self.head.as_ref() 类型是 Option<&Box<Node<T>>>,被包装的是引用类型
// 3 node 的类型是 &Box<Node<T>>
// 4 *node 的类型是 Box<Node<T>>
// 5 **node 的类型是 Node<T>
// 6 &**node 的类型是 &Node<T>
Iter {
next: self.head.as_ref().map(|node| &**node),
}
}
}
impl<'a, T> Iterator for Iter<'a, T> {
type Item = &'a T;
fn next(&mut self) -> Option<Self::Item> {
self.next.map(|node1| {
// 这里与 version4 的差别是: 增加了 as_ref()
// type Link<T> = Option<Box<Node<T>>>;
// 1 node1.next 类型是 Link<T>,即 Option<Box<Node<T>>>,被包装的是所有权类型
// 2 node1.next.as_ref() 类型是 Option<&Box<Node<T>>>,被包装的是引用类型
// 3 node2 的类型是 &Box<Node<T>>
// 4 *node2 的类型是 Box<Node<T>>
// 5 **node2 的类型是 Node<T>
// 6 &**node2 的类型是 &Node<T>
self.next = node1.next.as_ref().map(|node2| &**node2);
&node1.elem
})
}
}
}
4.4 强制解引用和泛型类型提示符
- 编译器在必要的时候可以自动在引用类型前增加一个或者多个解引用运算符(星号),使得类型匹配,这就是强制解引用(deref coercion)
- 但在4.3节中强制解引用没法完成工作:
self.next = node1.next.as_ref().map(|node2| &**node2);
- 右侧的node2类型为
&Box<Node<T>>
,加两级解引用运算符之后,类型为Node<T>
,经过map()方法得到Option<Node<T>>
- 与左侧要求的类型是
Option<&Node<T>>
相差一个引用
- 右侧的node2类型为
这时候可以使用
turbofish
运算符::<>:rust self.next = node.next.as_ref().map::<&Node<T>, _>(|node| &node);
因为map是一个带有泛型的方法
rust pub fn map<U, F>(self, f: F) -> Option<U>
turbofish运算符的作用,就是告诉编译器,泛型参数对应的具体类型应该是什么。这也让编译器知道,应该对
&node
中的node应用强制解引用,程序员就不用手动加两级解引用运算符了。5 最终代码```rust
//=========== Iter Version 6: 在 version5 的基础上,增加 turbofish 运算符,并且应用生命周期省略 // cargo test —features ver6
[cfg(feature = “ver6”)]
mod version6 { use super::*;
pub struct Iter<’a, T> {
next: Option<&'a Node<T>>,
}
impl
List { pub fn iter(&self) -> Iter<T> {
Iter {
next: self.head.as_ref().map::<&Node<T>,_>(|node| &node),
}
}
}
impl<’a, T> Iterator for Iter<’a, T> {
type Item = &'a T;
fn next(&mut self) -> Option<Self::Item> {
self.next.map(|node1| {
self.next = node1.next.as_ref().map::<&Node<T>,_>(|node2| &node2);
&node1.elem
})
}
} } ```
6 测试代码```rust
//========== 3.5 Iter ==========
[test]
fn iter() { let mut list = List::new(); list.push(1); list.push(2); list.push(3);
let mut iter = list.iter();
assert_eq!(iter.next(), Some(&3));
assert_eq!(iter.next(), Some(&2));
assert_eq!(iter.next(), Some(&1));
assert_eq!(iter.next(), None);
} ```