【Rust中级教程】1.11. 生命周期(进阶) Pt.1:回顾、借用检查器、泛型生命周期
1.11 生命周期(进阶) Pt.1回顾、借用检查器、泛型生命周期这篇文章在已有基础上对生命周期这一概念进行补充。1.11.1. 回顾在初级教程中我们提到过Rust里每个引用都有生命周期它就是引用保持合法的作用域(scope)大多数时候都是隐式并且由编译器推断出来的。对某个变量取得引用时生命周期开始当变量移动或离开作用域时生命周期结束。也就是对于某个引用来说它必须保持合法的一个代码区域的名称。生命周期通常与作用域重合但也不一定。1.11.2. 借用检查器(Borrow Checker)每当具有某个生命周期a的引用被使用借用检查器都会检查a是否还存活。具体方法是- 追踪路径到a开始获得引用的地方- 从这开始检查沿着路径是否存在冲突- 保证引用指向一个可安全访问的值这个例子使用了randcrate。在Cargo.toml中添加如下依赖[dependencies] rand 0.8看个例子use rand::random; fn main() { let mut x Box::new(42); let r x; if random::f32() 0.5 { *x 84; } else { println!({}, r); } }x是Boxi32类型把r声明为x的引用从这一行(第5行)开始引用的生命周期就开始了第7行对x进行了解引用修改值的操作需要一个指向x的可变引用。借用检查器此时就会去出一个指向x的可变引用并检查它的使用是否存在冲突这个代码例中没有冲突所以代码是合法的有人可能会问了第7行在r的作用域里*x需要指向x的可变引用那在同一个作用域里既有不可变引用r和可变引用*x违反了借用规则不应该报错吗事实上Rust很聪明知道代码如果走了if分支就不能走else分支了r在if分支下根本就没有使用过所以在if分支下使用*x这个可变引用是没有问题的。换句话说r的生命周期并没有延伸到if分支里。这就是生命周期通常不与作用域完全重合的例子再看一个例子fn main() { let mut x Box::new(42); let mut z x; for i in 0..100 { println!({}, z); x Box::new(i); z x; } println!({}, z); }x是Boxi32类型z是x的引用从这行(第4行)生命周期就开始了第6行循环中打印了z使用到了z这个引用自然就会被借用检查器检查。这里没有什么问题所以借用检查器不会报错第7行x被重新赋值第8行z被重新赋值Rust会把新赋的这个引用视作是另一个引用所以相当于第8行是新的生命周期而原本的生命周期到7行就结束了之后的每次循环都是z x;这一行会开启一个新的生命周期。借用检查器因此不会报错借用检查器的特性借用检查器是保守的如果不确定某个借用是否合法借用检查器就会拒绝该借用。借用检查器有时候会需要帮助来理解借用为什么是合法的这就是Unsafe Rust存在的部分原因。1.11.3. 泛型生命周期有时候我们需要在自己的类型里储存引用。我们就需要给这些引用标注生命周期以便借用检查器检查合法性。例如在类型方法中返回引用且存活时间比self长。Rust允许你基于一个或多个生命周期将类型的定义泛型化。两点提醒如果类型实现了Droptrait那么丢弃类型时就被记作是使用了类型所泛型的生命周期或类型。如果类型没有实现Droptrait那么类型丢弃时不会当作使用了生命周期可以忽略类型内的引用。举个例子某个类型实例要被丢弃了在丢弃之前借用检查器会查看是否仍然合法地去使用你类型的泛型生命周期因为你在drop函数中的代码可能会用到这些引用类型可泛型多个生命周期但通常没必要让类型签名变得更复杂。只有类型包含多个引用时你才应该使用多个生命周期参数并且返回的引用只应绑定到其中一个引用的生命周期。看个例子fn longesta(x: a str, y: a str) - a str { if x.len() y.len() { x } else { y } }a表示有a这么一个生命周期,x、y以及返回类型都是这个生命周期a这个时候就表示x、y和返回类型的生命周期是一样的。