我们定义一个函数compare,入参是两个i8类型的引用,并且返回两个参数中较大的那个:
fn main() {
let x = 1;
{
let y = 2;
let answer = compare(&x, &y);
println!("The larger value is: {}", answer);
}
}
fn compare(p: &i8, q: &i8) -> &i8 {
if p > q {
p
} else {
q
}
}
尽管上面定义的compare方法返回的引用对println!来说总是有效的(因为不管返回的是x还是y的引用,在println!语句中都是可以访问的),但是上面的代码无法通过编译,因为有可能只有其中的一种返回值是有效的(比如只有当返回x引用的时候是有效的)。来看一下下面的代码:
fn main() {
let x = i8::new(1);
let answer;
{
let y = i8::new(2);
answer = compare(&x, &y);
}
println!("The larger value is: {}", answer);
}
fn compare(p: &i8, q: &i8) -> &i8 {
if p > q {
p
} else {
q
}
}
如果返回y的引用,当调用println!输出的时候,answer引用的y已经超出了作用域失效了(内存被回收了),这样answer引用的就是一个无效的内存区域,Rust不允许出现这种悬垂引用。Rust使用生命周期参数来避免这种潜在的运行时错误。因为编译器无法提前知道到底运行时会执行if分支还是else分支(取决于运行时传入的p和q的大小关系,编译时是无法得知的)。上面的代码将无法编译,并且打印"a lifetime parameter is expected in compare’s signature.",即compare方法的签名中期望有一个生命周期参数。
范型生命周期参数给函数的入参引用和返回值引用施加了生命周期约束。当编译代码的时候,范型的生命周期会被具体的生命周期替换掉,它会等于传入的引用的生命周期中较小的那个引用的生命周期。这使得Rust能够识别违反函数参数和返回值生命周期约束的情况。
注解语法
生命周期参数的名字必须以单引号(')开头,并且在函数名字后边的尖括号中,紧接着是函数参数、返回的引用类型,比如:
// 函数foo,有一个生命周期范型参数'a,入参是x和y,它们都是有生命周期a的u8类型的引用,返回值是一个具有生命周期a的u8类型的引用
fn foo<'a>(x:&'a u8,y:&'a u8)-> &'a u8{
//函数体
}
现在,将范型生命周期加到compare函数中:
fn compare<'a>(p: &'a u8, q: &'a u8) -> &'a u8 {
if *p > *q {
p
} else {
q
}
}
fn main() {
let x = 1; //----------+outer
{ // |
let y = 2; //----+inner|
let answer = compare(&x, &y); // | |
println!("The larger value is: {}", answer); // | |
} //----+ |
} //----------+
传递给compare函数的两个引用有不同的生命周期(或者叫做作用域)。由于y有更小的生命周期,它的生命周期将变成具体的生命周期。也就是说,所有参数中带有’a的引用的生命周期都必须至少和y一样长。这也意味着,存储着返回值引用的answer变量的生命周期也要至少和y一样长(因为函数签名中说了,返回值引用的生命周期是’a)。
所以,如果我们在y的生命周期(作用域)之外使用answer变量将获得编译报错,比如如下代码:
fn compare<'a>(p: &'a u8, q: &'a u8) -> &'a u8 {
if *p > *q {
p
} else {
q
}
}
fn main() {
let x = 1; //----------+outer
let answer; // |
{ // |
let y = 2; //----+inner|
answer = compare(&x, &y); // | |
} //----+ |
println!("The larger value is: {}", answer); // |
} //----------+
将会编译报错:
--> src/main.rs:13:30
|
13 | answer = compare(&x, &y); // | |
| ^^ borrowed value does not live long enough
14 | } //----+ |
| - `y` dropped here while still borrowed
15 | println!("The larger value is: {}", answer); // |
| ------ borrow later used here
通常,在函数体内不需要显示指定生命周期,因为Rust能够分析清楚函数内部的代码。但是一个函数可能在程序中多处被调用,每处调用的地方,传入的参数和返回值的使用情况都不一定一样,这个时候就需要在函数签名中显示地指定参数与返回值的生命周期,需要注意的是,指定生命周期,并不会改变任何传入值或返回值的生命周期,而是指出了任何不满足这个约束条件的值都将被编译器的借用检查器(Borrow Checker)拒绝。
本文有部分内容翻译自:https://www.educative.io/answers/what-are-generic-lifetime-parameters-in-a-rust-function
生命周期相关资料:
1.https://course.rs/advance/lifetime/basic.html
2.《Rust编程 入门、实战与进阶》96页(朱春雷著)
本文介绍Rust中的生命周期参数概念,解释如何使用生命周期参数确保引用的有效性,并给出具体代码示例。

被折叠的 条评论
为什么被折叠?



