10.3 生命周期
Rust中的生命周期主要是用来解决悬垂引用的问题。首先我们来看看Rust中的悬垂引用问题怎么产生的。
10.3.1 悬垂引用
以下这个例子,我们在模块外声明了一个变量r
,然后在模块给他赋值w
的引用,但是模块里面的内容执行完就会销毁掉里面的内容,造成r
变量指向一个空指针 ,形成了悬垂引用,如下:
rust
let r; //----------'a
{ // |
let w = String::from("stephen");//-----'b |
r = &w; // | |
} //--- |
println!("{}", r); //--------+
// 值比变量的声明的范围小,造成悬垂引用
通过上面例子来看,当我们声明的变量比值的生命周期更长时,这个时候就会触发悬垂引用。那么Rust是怎么检测出来的呢?
10.3.2 生命周期标注规则
Rust有一个专门处理引用数据的检查器,叫借用检查器。它负责Rust整个生命周期的检查。
接下来我们就来了解一下生命周期的标注规则,我们一般使用' + 小写字母
来表示一个生命周期,例如: 'a
, 'b
等等。
当我们需要配合着&
使用,我们就需要在后面再加一个空格,例如:
rust
&str // 引用
&'a str // 带有生命周期标注的引用
&'a mut str // 带有生命周期标注的可变引用
10.3.3 省略规则
在实践之前,我们再来了解一下,借用检查机器内置的一些省略规则: 输入生命周期 类似 函数传参 输出生命周期 类似 函数返回值
第一条: 每个传入的参数都一个生命周期,传入一个参数有一个输入生命周期,传入两个参数则有两个生命周期
第二条: 如果只有一个参数,那么输出生命周期和输入生命周期相同
第三条: 在结构体 和枚举 的方法中,当拥有多个输入生命周期参数,而其中一个是&self
或&mut self
时,self的生命周期会被赋予给所有的输出生命周期参数。
10.3.4 函数添加生命周期标注
接下来我们尝试写一个比较两段字符串长度的函数,例如:
rust
fn longest(str1: &str, str2: &str) -> &str {
if str1.len() > str2.len() {
str1
} else {
str2
}
}
但是因为这里传入的str1
和str2
的引用,我们并不知道他们的生命周期、什么时候会销毁,所以这里返回值,很有可能是一个悬垂引用,所以会编译失败。 我们来给他添加生命周期,保证引用的值比函数后面销毁,例如:
rust
fn longest<'a>(str1: &'a str, str2: &'a str) -> &'a str {
if str1.len() > str2.len() {
str1
} else {
str2
}
}
这我们将str1
和str2
、返回值的生命周期都设置为'a
,然后就通过借用检查器的编译了。
10.3.5 结构体添加生命周期标注
我们在之前的结构体章节里面定义一个了一个User
结构体,如下:
rust
struct User {
name: String,
}
当时我们没有使用&str
,因为我们还没有设计到生命周期的概念,现在我们使用&str
来代替这个name
字段,例如:
rust
struct User<'a> {
name: &'a str,
}
10.3.7 静态生命周期
我们还可以在Rust中定义一个特别的生命周期'static
,它只作用于字符串字面量,并且它作用于小程序从周期开始到结束,例如:
rust
let str1: &'static str = "stephen";
println!("{}", str1);
注意 我们要慎用这个静态类型,它不仅会增加存储的时间周期、增加内存的消耗,作用于全局还会绕过借用检查器的规则。
10.3.8 泛型,特征,生命周期结合使用
最后,让我们来写一个泛型,特征,生命周期结合的函数结束第10节的学习。我们来改造改造之前的longest
函数,如下:
rust
fn longest<'a, T>(str1: &'a str, str2: &'a str, str3: T) -> &'a str
where
T: Display,
{
println!("extra={}", str3);
if str1.len() > str2.len() {
str1
} else {
str2
}
}
let s1 = String::from("stephen");
let s2 = String::from("james");
let long = longest(&s1, &s2, 2);
println!("{}", long);