rust之生命周期
前言
看了好久的死灵书和官网教程,总算可以写这部分了。
为什么需要生命周期
上一篇《rust所有权和生命周期》我们主要讲到了所有权,但是光有所有权不能保证内存安全。对于引用的有效性没办法界定和保证。会导致的以下问题:
- 悬挂指针:指针指向的内存空间已经无效了
- 数据竞争: 在多线程中,无法保证数据的预期结果。在单一线程中,保证可变引用不会同时修改相同内存。不可变引用的值是一致的,不会被可变引用修改。
rust无法对所有的情况做出推导,需要我们自己去界定一些引用的生命周期。编译器再通过这些标识做引用的有效性检查。
什么是生命周期
生命周期是编译器(引用检查系统)用来确保引用是有效的机制。官网英文定义:
A lifetime is a construct the compiler (or more specifically, its borrow checker) uses to ensure all borrows are valid
我们在rust所有权那篇讲过,引用的使用有两个限制。引用的有效性是通过生命周期来保证的。
- 引用的唯一性。
- 引用的有效性。
生命周期的语法
引用检查系统使用具体的生命周期注解来决定引用有效性的时间。在生命周期注解不能够省略的地方,Rust要求具体的注解来声明引用的生命周期。
生命周期周期的语法是'+标识,例如声明生命周期周期a 'a
引用的生命周期注解
在引用符号后面加上生命周期声明即可,例如: x : &'a i32
函数的生命周期注解
foo<'a>(x: &'a i32)
或者多个注解
foo<'a, 'b>()
方法的生命周期注解
方法的生命周期注解和函数类似
struct Owner(i32);
impl Owner {
// Annotate lifetimes as in a standalone function.
fn add_one<'a>(&'a mut self) { self.0 += 1; }
fn print<'a>(&'a self) {
println!("`print`: {}", self.0);
}
}
fn main() {
let mut owner = Owner(18);
owner.add_one();
owner.print();
}
结构体的生命周期注解
结构体和枚举类型的生命周期也是类似的
#[derive(Debug)]
struct Borrowed<'a>(&'a i32);
// Similarly, both references here must outlive this structure.
#[derive(Debug)]
struct NamedBorrowed<'a> {
x: &'a i32,
y: &'a i32,
}
// An enum which is either an `i32` or a reference to one.
#[derive(Debug)]
enum Either<'a> {
Num(i32),
Ref(&'a i32),
}
fn main() {
let x = 18;
let y = 15;
let single = Borrowed(&x);
let double = NamedBorrowed { x: &x, y: &y };
let reference = Either::Ref(&x);
let number = Either::Num(y);
println!("x is borrowed in {:?}", single);
println!("x and y are borrowed in {:?}", double);
println!("x is borrowed in {:?}", reference);
println!("y is *not* borrowed in {:?}", number);
}
trait的生命周期注解
trait的生命周期注解,要在impl关键字后面声明
// A struct with annotation of lifetimes.
#[derive(Debug)]
struct Borrowed<'a> {
x: &'a i32,
}
// Annotate lifetimes to impl.
impl<'a> Default for Borrowed<'a> {
fn default() -> Self {
Self {
x: &10,
}
}
}
fn main() {
let b: Borrowed = Default::default();
println!("b is {:?}", b);
}
生命周期的省略
在某些情况下,引用检查系统是可以推断出函数/方法中引用的生命周期的,这时候我们可以省略声明生命周期。
存在函数或方法参数中的生命周期声明叫做input lifetimes,存在return处的叫output lifetimes。
存在三个规则下的生命周期声明可以省略:
- 规则一:每个参数都有自己的生命周期声明,例如
fn foo<'a>(x: &'a i32)
,fn foo<'a, 'b>(x: &'a i32, y: &'b i32)
- 规则二:只有一个input lifetime,并且这个lifetime作用给所有output lifetimes,例如
fn foo<'a>(x: &'a i32) -> &'a i32
- 规则三:如果有多个input lifetimes,但是其中一个是&self,或者是&mut self。并且output lifetimes的生命周期是和self一致的。
[me]:我自己实验发现,方法的output lifetimes默认是和self一致的。如果不声明input lifetimes的生命周期是单独的。