生命周期可以说是Rust中最特别的性质,也是Rust中比较难(反人类)的部分。
生命周期(一)
关于生命周期,我们应该知道:
- Rust的每个引用都有自己的生命周期
- 生命周期:引用保持有效的作用域
- 大多数情况:生命周期是隐式的、可被推断的
- 当引用的生命周期可能以不同的方式互相关联时,我们需要手动标注生命周期
生命周期的目的
生命周期最主要的目的就是避免悬垂引用(dangling reference)
let r;
{
let x = 5;
r = &x; //`x` does not live long enough borrowed value does not live long enough
}
println!("r: {}", r);
上面会报错是因为在调用r的时候,x已经走出作用域,也就是被销毁了。而r被初始化为x的引用实际上就是一个悬垂引用
那么Rust是如何进行判断的呢?
借用检测器
Rust编译器的借用检查器:比较作用域来判断所有的借用是否合法。
还是上面的例子,x的生命周期为b,r的生命周期为a。b的生命周期比a短。所以不合法。
如果要合法,那么x的生命周期至少不应该比r短。我们可以这样改动:
let x = 5;
let r = &x;
println!("r: {}", r);
函数中的泛型的生命周期
我们现在要写一个比较两个字符串长度的,并且返回长的那一个。逻辑很简单,我们可能会这样写:
fn main() {
let s1 = "sss";
let s2 = String::from("yesyesyes");
println!("{}", longest(s1, s2.as_str()));
}
fn longest(s1: &str, s2: &str) -> &str {
if s1.len() > s2.len() {
s1
} else {
s2
}
}
但是却报错了,报错的原因是:此函数的返回类型包含借用的值,但签名没有说明它是从“S1”还是“S2”借用的。但是我们将其返回值为一个固定的,依然会报同样的错:
fn longest(s1: &str, s2: &str) -> &str {
s2
}
所以这与函数体的逻辑无关,而是与函数签名有关。我们在这里就必须使用泛型的生命周期来编写代码:
fn longest<'a>(s1: &'a str, s2: &'a str) -> &'a str {
if s1.len() > s2.len() {
s1
} else {
s2
}
}
生命周期的标注语法
生命周期的标注不会改变引用的生命周期长度
当指定了泛型生命周期参数,函数可以接收带有任何生命周期的引用
生命周期的标注:描述了多个引用的生命周期间的关系,但不影响生命周期
生命周期标注的语法
生命周期参数名:
- 以单引号
'
开头 - 通常全小写且非常短
- 通常情况下使用
'a
生命周期标注的位置:
- 在引用的&符号后
- 使用空格将标注和引用类型分开
&i32
一个引用,&'a i32
带有显式生命周期的引用,&'a mut i32
带有显式生命周期的可变引用
单个生命周期标注本身没有意义,标注之所以存在,是为了向rust描述多个泛型生命周期参数间的关系
fn longest<'a>(s1: &'a str, s2: &'a str) -> &'a str {
if s1.len() > s2.len() {
s1
} else {
s2
}
}
比如上面这个例子中就是说s1和s2这两个引用必须不能短于泛型生命周期'a
函数签名中的生命周期标注
泛型生命周期参数声明在:函数名和参数列表之间的<>
里(泛型参数)
现在我们改动一下上面的例子:
fn main() {
let s1 = String::from("yesyesyes");
{
let s2 = "sss";
let result = longest(s1.as_str(), s2);
println!("{}", result);
}
}
fn longest<'a>(s1: &'a str, s2: &'a str) -> &'a str {
if s1.len() > s2.len() {
s1
} else {
s2
}
}
这里依然能通过,s1->2-8,s2生命周期为静态的生命周期(static),在整个程序运行期间都存活
&str是直接在可执行文件中加载的,所以指这块内存的引用,一定会一直指向一个合法内存,所以其引用的生命周期是'static,也就是全局静态
我们再改动一下代码:
fn main() {
let s1 = String::from("yesyesyes");
let result;
{
let s2 = String::from("sss");
result = longest(s1.as_str(), s2.as_str());
}
println!("{}", result);
}
fn longest<'a>(s1: &'a str, s2: &'a str) -> &'a str {
if s1.len() > s2.len() {
s1
} else {
s2
}
}
我们将result声明在块外,println也在块外执行。到目前为止还没报错,但当我们将s2的类型转换为String类型的时候就报错了。
我们之前了解到:<'a>
的生命周期是取的s1和s2中短的那一个。在这个例子中,'a
的生命周期就是s2的生命周期,也就是5-7
使用cargo run来看看爆的是什么错误:
我们看到,s2被借用了,但它的生命周期不够长。s2在离开作用域后也就是第7行后,在第8行依然被发生了借用,所以就报错了
我们只需要记住:生命周期'a
的实际生命周期是:x和y两个生命周期中较小的那个