letr; // 生命周期 'a 开始
{
letx = 5; // 生命周期 'b 开始
r = &x; // 编译错误!'b 比 'a 短
} // 生命周期 'b 结束// 使用 r // 'a 继续有效,但 x 已被释放
生命周期的本质
隐式性:大多数情况下,生命周期都是隐式的,可以被编译器推断
关系描述:生命周期注解不改变引用的实际生存时间,只是描述多个引用之间的关系
安全保证:确保数据存活的时间完全能够覆盖其引用的存活时间
为什么需要生命周期?
防止悬垂引用:确保引用不会指向已被释放的内存
内存安全:在编译时捕获潜在的内存安全问题
零成本抽象:生命周期检查在编译时完成,没有运行时开销
悬垂引用问题
什么是悬垂引用?
悬垂引用 (Dangling References) 是指引用指向已经被释放或不再有效的内存区域:
fnmain() {
letr; // 外层作用域声明引用
{
letx = 5; // 内层作用域创建值
r = &x; // 编译错误!x 的生命周期比 r 短
} // x 在这里被释放println!("{}", r); // 尝试使用悬垂引用
}
编译器的错误信息
error[E0597]: `x` does not live long enough --> src/main.rs::
|| r = &x;
| ^^ borrowed value does live enough
| }
| - `x` dropped here still borrowed
| println!(, r);
| - borrow later used here
'返回了生命周期' -> 编译器拿到了'契约'当你写下 fn longest<'a>(...) -> &'a str 时,你实际上是和编译器签了一份契约:'我(函数作者)保证:不管函数内部怎么跑,返回的这个引用,它的有效期绝对不会超过输入参数 x 和 y 中较短的那个。'
'使用者就知道了' -> 编译器在调用点进行'验算'当别人(或者你自己)在别的地方调用这个函数时,编译器会立刻拿着这份契约去验算:输入检查:看看你传入的 x 和 y 分别能活多久?推导输出:根据契约,算出返回值理论上能活多久(即 min(x, y))。使用检查:看看你在代码后面使用这个返回值的地方,是否还在它理论的有效期内?
fnmain() {
letstring1 = String::from("long string is long");
{
letstring2 = String::from("xyz");
letresult = longest(string1.as_str(), string2.as_str());
println!("The longest string is {}", result); // result 在 string2 有效的期间有效
}
// 如果这样写就会编译错误:// let result = longest(string1.as_str(), string2.as_str());// println!("The longest string is {}", result);// 因为 result 的生命周期与 string2 相同,而 string2 已经被释放
}
结构体中的生命周期
结构体字段的生命周期注解
当结构体包含引用类型字段时,必须添加生命周期注解:
structImportantExcerpt<'a> {
part: &'astr,
}
生命周期注解的含义
structImportantExcerpt<'a> {
part: &'astr,
}
fnmain() {
letnovel = String::from("Call me Ishmael. Some years ago...");
letfirst_sentence = novel.split('.').next().expect("Could not find a '.'");
leti = ImportantExcerpt {
part: first_sentence,
};
// ImportantExcerpt 实例的生命周期不能超过 part 引用的生命周期
}