生命周期是另一种泛型,不同于这里描述的泛型,生命周期是确保引用在使用的过程中是有效的。
使用生命周期确保引用的有效性
引用是不具有所有权的,那么如果使用引用的时候,它所引用的对象已经销毁了,那么肯定会发生未定义的错误。为了避免这个问题出现,Rust就通过生命周期来确保引用在使用期间一直有效。
之所以有的时候前面没有提到生命周期,那是因为大多数时候,生命周期是可以被推导出来的。
避免悬垂引用
{
let r;
{
let x = 5;
r = &x;
}
println!("r: {}", r);
}如上代码,在x销毁之前使用r指向了x的引用。这是肯定不会编译通过的,因为引用的生命周期不能超过其引用的对象。
借用检查器
Rust通过借用检查器的工具来检查所有借用的合法性 每一个数据或者引用都有一个生命周期,借用检查器检查它们的作用域来检查生命周期。引用的生命周期要小于其引用的数据,因为引用要一直保持有效。
函数中的泛型生命周期
给定一段代码,获取两个字符串切片中较长的那个
fn longest(x:&str, y:&str) -> &str {
if x.len() > y.len() {
x
} else {
y
}
}但是上面的代码无法通过编译,因为返回类型是&str,Rust并不能确定返回的引用指向x还是y。那么借用检查器就无法分析返回的引用的作用域,也就无法确定返回的引用的有效性。
通过引入泛型生命周期来解决这一点。
生命周期的标注必须以'开头,并且使用全小写字符。
带生命周期的引用示例:
&i32 //引用
&'a i32 //带有显式生命周期的引用
&'a mut i32 生命周期也是泛型,将本节的例子加上泛型生命周期进行改写
fn longest<'a>(x: &'a str, y:'a str) -> &'a str {
if x.len() > y.len() {
x
} else {
y
}
}只要传入的参数保证生命周期不短于'a即可,同时编译器可以知道返回的引用的生命周期不短于'a,这样借用检查器就能够进行借用检查,确保引用的有效性了。
深入理解生命周期
只要时刻牢记一点,生命周期是为了借用检查器能够检查搜索借用的正确性,确保引用的有效。
只要代码的实现中能够推断出所有引用的生命周期,那么这个代码的生命周期写法就是可以通过编译的。
结构体中的生命周期标注
结构体中如果有字段是引用类型,那么这个结构体的定义就需要泛型生命周期标注。例如:
struct ImportantExcerpt<'a> {
part: &'a str,
}
fn main() {
let novel = String::from("Call me Ishmael. Some years ago...");
let first_sentence = novel.split('.')
.next()
.expect("Could not find a '.'");
let i = ImportantExcerpt {
part: first_sentence
};
}结构体的泛型生命周期标注,表明了结构体的示例的存活时间不能够超过其内部引用字段的存活时间。
生命周期省略
为了简便,Rust为提供了生命周期省略规则,自动推导生命周期,如果推导出来没有歧义的话,就不需要显式标注生命周期了,否则需要标注。
- 每个引用参数都有自己的生命周期参数。
- 当只存在一个输入生命周期参数时,这个生命周期可以被赋予给所有输出生命周期参数。
- 当拥有多个输入生命周期参数,其中一个是
&self或者&mut self的时候,self的生命周期会赋予所有的输出生命周期参数。
静态生命周期
Rust中存在一种特殊的生命周期,静态生命周期'static,表示整个程序的执行期。字符串字面量的生命周期就是'static。
同时使用泛型、trait约束与生命周期
一个同时使用三者的例子如下
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
}
}