生命周期-Rust

生命周期是为了避免悬垂引用

了每一个引用&都有一个生命周期

悬垂引用

悬垂引用指的是一个引用指向的内存已经被释放或者不再有效,编程者如果在这个时候还尝试访问这个失效的引用就会发生错误

r 引用的值在尝试使用之前就离开了作用域

image-20241230132546458

借用检查器

Rust 编译器有一个 借用检查器(borrow checker),它比较作用域来确保所有的借用都是有效的

r 的生命周期标记为 ‘a 并将 x 的生命周期标记为 ‘b

被引用者的生命周期要大于或者等于引用者的生命周期,或者直接将所有权交给引用者

image-20241230132835435

函数中的泛型生命周期

编写一个返回两个字符串 slice 中较长者的函数

image-20241230134230542我们定义这个函数的时候,并不知道传递给函数的具体值,所以也不知道到底是 if 还是else 会被执行

1
2
3
&i32 // 引用
&'a i32 // 带有显式生命周期的引用
&'a mut i32 // 带有显式生命周期的可变引用

单个的生命周期注解本身没有多少意义,因为生命周期注解告诉 Rust 多个引用的泛型生命周期参数如何相互联系的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
fn main() {

let string1 = String::from("abcd");

let string2 = "xyz";

let result = longest(string1.as_str(), string2);

println!("The longest string is {result}");
}


fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {//这两个参数和返回的引用存活的一样久
if x.len() > y.len() {
x
} else {
y
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
fn main() {
let string1 = String::from("long string is long");
let result;
{
let string2 = String::from("xyz");
result = longest(string1.as_str(), string2.as_str());
}//string2的生命周期到此结束
println!("The longest string is {result}");//result作为返回值生命周期超过了最短的string2,所以会报错,注释改行代码就会编译通过
}


fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {//我们告诉借用检查器生命周期要以最短的那个为限制
if x.len() > y.len() {
x
} else {
y
}
}

深入理解生命周期

如果将 longest 函数的实现修改为总是返回第一个参数而不是最长的字符串 slice,就不需要为参数 y 指定一个生命周期

1
2
3
fn longest<'a>(x: &'a str, y: &str) -> &'a str {
x
}

当从函数返回一个引用,返回值的生命周期参数需要与一个参数的生命周期参数相匹配。如果返回的引用没有 指向任何一个参数,那么唯一的可能就是它指向一个函数内部创建的值。然而它将会是一个悬垂引用,因为它将会在函数结束时离开作用域。这种情况即使指定生命周期编译也通不过,如下例子

1
2
3
4
fn longest<'a>(x: &str, y: &str) -> &'a str {
let result = String::from("really long string");
result.as_str()
}

我们尝试从函数返回一个 result 的引用。无法指定生命周期参数来改变悬垂引用,而且 Rust 也不允许我们创建一个悬垂引用。在这种情况,最好的解决方案是返回一个有所有权的数据类型而不是一个引用,这样函数调用者就需要负责清理这个值了。

结构体中的生命周期

1
2
3
struct ImportantExcerpt<'a> {
part: &'a str,
}

这个注解意味着 ImportantExcerpt 的实例不能比其 part 字段中的引用存在的更久

生命周期省略

函数或方法的参数的生命周期被称为 输入生命周期(input lifetimes),而返回值的生命周期被称为 输出生命周期(output lifetimes)

三条默认的生命周期规律

1
2
3
1、函数有一个引用参数的就有一个生命周期参数:fn foo<'a>(x: &'a i32),有两个引用参数的函数就有两个不同的生命周期参数,fn foo<'a, 'b>(x: &'a i32, y: &'b i32),依此类推
是如果只有一个输入生命周期参数,那么它被赋予所有输出生命周期参数:fn foo<'a>(x: &'a i32) -> &'a i32
是如果方法有多个输入生命周期参数并且其中一个参数是 &self 或 &mut self,说明是个对象的方法 (method),那么所有输出生命周期参数被赋予 self 的生命周期

静态生命周期

‘static,其生命周期能够存活于整个程序期间

总结

在同一函数中指定泛型类型参数、trait bounds 和生命周期

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
fn main() {
let string1 = String::from("abcd");
let string2 = "xyz";

let result = longest_with_an_announcement(
string1.as_str(),
string2,
"Today is someone's birthday!",
);
println!("The longest string is {result}");
}

use std::fmt::Display;
fn longest_with_an_announcement<'a, T>(
x: &'a str,
y: &'a str,
ann: T,
) -> &'a str
where
T: Display,
{
println!("Announcement! {ann}");
if x.len() > y.len() {
x
} else {
y
}
}