Rust 生命周期
1. 什么是生命周期?
生命周期是引用有效的作用域范围。在 Rust 中,每个引用都有一个生命周期,这是它保持有效的作用域。
fn main() {
// 生命周期开始
let x = 5; // x 的生命周期开始
let r = &x; // r 的生命周期开始,引用 x
println!("r = {}", r);
// 生命周期结束
} // x 和 r 的生命周期结束
2. 为什么需要生命周期?
问题:悬垂引用(Dangling References)
// 这个代码无法编译!
fn main() {
let reference;
{
let value = 42; // value 在内部作用域创建
reference = &value; // reference 引用 value
} // value 在这里被丢弃,内存被释放
// 错误!reference 现在指向已释放的内存
println!("reference: {}", reference);
}
编译器错误:
error[E0597]: `value` does not live long enough
生命周期的目的
- 防止悬垂引用
- 确保内存安全
- 在编译时进行检查
3. 生命周期注解语法
基本语法
&i32 // 普通引用
&'a i32 // 带有显式生命周期的引用
&'a mut i32 // 带有显式生命周期的可变引用
生命周期参数命名
- 以
'开头 - 通常使用短名称:
'a,'b,'c 'static是特殊生命周期
4. 函数中的生命周期
问题场景
// 无法编译:不知道返回的引用与哪个参数相关
fn longest(x: &str, y: &str) -> &str {
if x.len() > y.len() {
x
} else {
y
}
}
解决方案:添加生命周期参数
// 正确版本
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() {
x
} else {
y
}
}
详细解释 <'a>
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str
<'a>:声明一个生命周期参数'ax: &'a str:参数 x 是一个字符串切片,具有生命周期'ay: &'a str:参数 y 也是一个字符串切片,具有相同的生命周期'a-> &'a str:返回值也具有生命周期'a
实际含义:返回的引用将和两个输入参数中生命周期较短的那个一样长。
使用示例
fn main() {
let string1 = String::from("很长的字符串");
{
let string2 = String::from("短字符串");
let result = longest(string1.as_str(), string2.as_str());
println!("最长的字符串是: {}", result);
// result 的生命周期与 string2 相同
} // string2 和 result 在这里离开作用域
// 这里不能再使用 result
}
5. 多个生命周期参数
// 两个参数有不同的生命周期
fn multiple_lifetimes<'a, 'b>(x: &'a str, y: &'b str) -> &'a str {
println!("y = {}", y); // 只是使用 y,但不返回它
x // 只返回 x,所以返回类型是 &'a str
}
fn main() {
let string1 = String::from("字符串1");
let result;
{
let string2 = String::from("字符串2");
result = multiple_lifetimes(string1.as_str(), string2.as_str());
} // string2 离开作用域,但 result 仍然有效,因为它只依赖 string1
println!("result = {}", result); // 可以正常使用
}
6. 结构体中的生命周期
包含引用的结构体
// 结构体包含引用,需要生命周期注解
struct Book<'a> {
title: &'a str,
author: &'a str,
}
fn main() {
let title = String::from("Rust 编程");
let author = String::from("张三");
let book = Book {
title: &title,
author: &author,
};
println!("《{}》 by {}", book.title, book.author);
}
重要规则
结构体实例不能比它包含的引用活得更久
fn create_book() -> Book<'static> {
let title = String::from("临时标题"); // title 在函数结束时被丢弃
let author = String::from("临时作者"); // author 在函数结束时被丢弃
// 错误!返回的 Book 包含对局部变量的引用
Book {
title: &title, // title 将在函数结束时被释放
author: &author, // author 将在函数结束时被释放
}
} // title 和 author 在这里被丢弃
7. 方法实现中的生命周期
struct ImportantExcerpt<'a> {
part: &'a str,
}
// 为带有生命周期的结构体实现方法
impl<'a> ImportantExcerpt<'a> {
// 规则3适用:返回值的生命周期与 &self 相同
fn get_part(&self) -> &str {
self.part
}
// 多个参数的情况
fn announce_and_return_part(&self, announcement: &str) -> &str {
println!("请注意: {}", announcement);
self.part
}
// 静态方法,不依赖 self
fn new(part: &'a str) -> ImportantExcerpt<'a> {
ImportantExcerpt { part }
}
}
8. 生命周期省略规则
Rust 编译器有3条自动推断规则,避免不必要的生命周期注解:
规则1:每个引用参数获得自己的生命周期
fn first_word(s: &str) -> &str
// 编译器推断为:
fn first_word<'a>(s: &'a str) -> &'a str
规则2:如果只有一个输入生命周期,它被赋予所有输出生命周期
fn first_word(s: &str) -> &str
// 应用规则2后:
fn first_word<'a>(s: &'a str) -> &'a str
规则3:如果有多个输入生命周期,但其中一个是 &self 或 &mut self,那么 self 的生命周期被赋予所有输出生命周期
impl SomeStruct {
fn get_value(&self, key: &str) -> &str
// 应用规则3后:
fn get_value<'a, 'b>(&'a self, key: &'b str) -> &'a str
}
9. 手动生命周期注解的场景
需要显式注解的情况:
- 函数返回引用,且输入参数有多个引用
- 结构体包含引用
- trait 对象包含引用
- 闭包涉及引用
// 必须手动注解的例子
fn problematic_function<'a, 'b>(x: &'a str, y: &'b str) -> &'a str {
// 复杂的逻辑...
x
}
10. 静态生命周期 'static
'static 生命周期表示整个程序的持续时间。
// 字符串字面量有 'static 生命周期
let s: &'static str = "我是字符串字面量";
// 函数返回 'static 生命周期
fn get_static_str() -> &'static str {
"我会永远存在"
}
// 在结构体中使用
struct GlobalData {
name: &'static str,
version: &'static str,
}
fn main() {
let data = GlobalData {
name: "MyApp",
version: "1.0.0",
};
}
11. 实际示例分析
示例1:解析字符串
fn parse_first_number<'a>(s: &'a str) -> Option<&'a str> {
for (i, c) in s.char_indices() {
if c.is_digit(10) {
let start = i;
let end = s[i..].find(|c: char| !c.is_digit(10))
.map(|pos| i + pos)
.unwrap_or(s.len());
return Some(&s[start..end]);
}
}
None
}
fn main() {
let text = String::from("这里有123个苹果和456个香蕉");
if let Some(number) = parse_first_number(&text) {
println!("找到数字: {}", number);
// number 的生命周期与 text 相同
}
}
示例2:缓存系统
struct Cache<'a, T> {
data: &'a [T],
timestamp: u64,
}
impl<'a, T> Cache<'a, T> {
fn new(data: &'a [T]) -> Self {
Cache {
data,
timestamp: std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_secs(),
}
}
fn get_data(&self) -> &'a [T] {
self.data
}
}
12. 常见错误和解决方案
错误1:返回局部变量的引用
// 错误!
fn bad_function() -> &str {
let s = String::from("hello");
&s // s 将在函数结束时被丢弃
}
// 正确:返回 String 而不是引用
fn good_function() -> String {
let s = String::from("hello");
s
}
错误2:生命周期不匹配
// 错误!
fn process_data<'a>(data: &'a str, default: &str) -> &'a str {
if data.is_empty() {
default // 错误:default 可能比 'a 生命周期短
} else {
data
}
}
// 正确:确保两个参数有相同的生命周期约束
fn process_data<'a>(data: &'a str, default: &'a str) -> &'a str {
if data.is_empty() {
default
} else {
data
}
}
贵公网安备52052402000220号