生命周期基础
学习目标
- 理解生命周期是什么
- 掌握函数签名中的生命周期标注
- 理解生命周期省略规则
- 掌握结构体中的生命周期
核心概念
为什么需要生命周期?
编译器需要确保引用不会比它指向的数据活得更久:
fn longest(x: &str, y: &str) -> &str { // ❌ 编译器不知道返回值的生命周期
if x.len() > y.len() { x } else { y }
}
编译器问:返回的引用指向 x 还是 y?如果 x 先被 drop,返回的引用就悬垂了。
生命周期标注
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
// ^^ 生命周期参数
// ^^ x 和 y 至少活这么久
// ^^ 返回值也至少活这么久
if x.len() > y.len() { x } else { y }
}
含义:返回的引用的生命周期 = x 和 y 中较短的那个。
fn main() {
let string1 = String::from("long string");
let result;
{
let string2 = String::from("xyz");
result = longest(string1.as_str(), string2.as_str());
println!("更长的是: {}", result); // ✅ string2 还活着
}
// println!("{}", result); // ❌ string2 已被 drop
}
不需要生命周期的情况
// 返回值只跟第一个参数有关 → 只需要标注一个
fn first_word(s: &str) -> &str {
// 编译器自动推断(生命周期省略规则)
let bytes = s.as_bytes();
for (i, &byte) in bytes.iter().enumerate() {
if byte == b' ' {
return &s[0..i];
}
}
&s[..]
}
生命周期省略规则
编译器按三条规则自动推断,不需要手动标注:
规则 1:每个引用参数都有自己的生命周期
fn f(x: &str, y: &str) → fn f<'a, 'b>(x: &'a str, y: &'b str)
规则 2:只有一个输入生命周期,它赋给所有输出
fn f(x: &str) -> &str → fn f<'a>(x: &'a str) -> &'a str
规则 3:方法中 &self 的生命周期赋给所有输出
fn f(&self, x: &str) -> &str → fn f<'a>(&'a self, x: &str) -> &'a str
如果三条规则都不能确定返回值的生命周期,编译器报错,需要手动标注。
结构体中的生命周期
结构体持有引用时,必须标注生命周期:
struct Excerpt<'a> {
text: &'a str, // 这个引用至少活到 Excerpt 实例被 drop
}
fn main() {
let novel = String::from("Call me Ishmael. Some years ago...");
let first_sentence;
{
let i = novel.find('.').unwrap_or(novel.len());
first_sentence = Excerpt {
text: &novel[..i],
};
}
println!("{}", first_sentence.text);
}
'static 生命周期
// 'static 表示整个程序运行期间都有效
let s: &'static str = "我存在整个程序生命周期";
// 字符串字面量都是 'static
let greeting: &'static str = "hello";
实践练习
练习 1:修复生命周期错误
// ❌ 编译错误,修复它
fn first_or_second(first: &str, second: &str, use_first: bool) -> &str {
if use_first { first } else { second }
}
// ✅ 修复
fn first_or_second<'a>(first: &'a str, second: &'a str, use_first: bool) -> &'a str {
if use_first { first } else { second }
}
练习 2:结构体生命周期
struct Highlight<'a> {
text: &'a str,
color: &'a str,
}
impl<'a> Highlight<'a> {
fn render(&self) -> String {
format!("[{}]{}", self.color, self.text)
}
}
fn main() {
let text = String::from("重要信息");
let h = Highlight {
text: &text,
color: "red",
};
println!("{}", h.render());
}
练习 3:理解省略规则
// 以下函数都不需要手动标注生命周期,为什么?
fn get_first(s: &str) -> &str { // 规则 2 适用
s.split_whitespace().next().unwrap_or("")
}
struct Parser {
input: String,
}
impl Parser {
fn remaining(&self) -> &str { // 规则 3 适用
&self.input
}
}
常见错误
1. 返回局部变量的引用
// ❌ s 在函数结束时被 drop
fn create() -> &str {
let s = String::from("hello");
&s
}
// ✅ 返回所有权
fn create() -> String {
String::from("hello")
}
2. 生命周期不匹配
fn longest<'a>(x: &'a str, y: &str) -> &'a str {
x // ✅ 可以返回 x
// y // ❌ 不能返回 y,因为 y 的生命周期和 'a 不关联
}
3. 过度标注
// ⚠️ 不需要标注(编译器能推断)
fn first<'a>(s: &'a str) -> &'a str {
&s[..1]
}
// ✅ 省略
fn first(s: &str) -> &str {
&s[..1]
}
小结
| 概念 | 说明 |
|---|---|
'a | 生命周期参数 |
&'a T | 生命周期为 'a 的引用 |
'static | 整个程序运行期间有效 |
| 省略规则 | 编译器自动推断,大多数情况不需要手动标注 |
生命周期的本质:告诉编译器多个引用之间的存活关系,确保不会悬垂。