
生命周期:引用的"保质期",过期无效
如果你写过返回引用的函数,可能遇到过这个错误:
fn get_first(s: &String) -> &str {
&s[..]
}
编译器:"缺少生命周期说明符!" 你:"啥周期?我就是要返回个切片啊!" 编译器:"不说清楚这个引用能活多久,我不放心!"
这就是 Rust 的**生命周期(Lifetime)**系统。听起来很高大上,其实就是编译器在问:"你这个引用能保证多久有效?别给我返回个'过期引用'!"
今天咱们就来搞懂生命周期的基础概念,让你不再被编译器的"生命周期"警告吓到。
生命周期是引用的有效作用域。它告诉编译器:"这个引用至少能活这么久"。
生活化类比:
{
let r; // ---------+-- 'a
// |
{ // |
let x = ; // -+-- 'b |
r = &x; // | |
} // -+ |
// |
println!("{}", r); // |
} // ---------+
这里 'b 比 'a 短,x 在内层作用域结束时就没了,但 r 还想用它——这就是悬垂引用,编译器会阻止。
编译器需要知道引用的有效期,以防止悬垂引用:
fn dangle() -> &String {
let s = String::from("hello");
&s // s 在函数结束时被 drop,返回的引用指向无效内存
}
编译器错误:
error[E0106]: missing lifetime specifier
--> src/main.rs:1:16
|
1 | fn dangle() -> &String {
| ^ expected named lifetime parameter
人话翻译:编译器:"你返回的引用指向谁?不说清楚生命周期,我怎么知道它会不会变成悬垂引用?"
生命周期注解用单引号 + 字母表示,如 'a、'b、'static。
// 基本语法
&'a i32// 引用,生命周期 'a
&'a mut i32// 可变引用
&'a str// 字符串切片
重点:生命周期注解是给编译器看的,不是给运行时的。它不改变程序行为,只帮助编译器检查。
当函数有多个引用参数和返回引用时,需要标注生命周期:
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() {
x
} else {
y
}
}
解读:
'a 是生命周期参数(类似泛型)x 和 y 的生命周期都是 'a'a含义:返回的引用至少和 x、y 中较短的那个一样长。
好消息:大多数情况不需要写生命周期!编译器有三条省略规则:
规则 1:每个引用参数获得独立的生命周期
fn first_word(s: &str) -> &str { ... }
// 编译器推断为:
fn first_word<'a>(s: &'a str) -> &'a str { ... }
规则 2:如果只有一个输入生命周期,它被赋给所有输出生命周期
fn longest(x: &str, y: &str) -> &str { ... }
// 编译器无法推断!两个输入生命周期,不知道返回哪个
规则 3:如果有 &self 或 &mut self,self 的生命周期赋给所有输出生命周期
impl<'a> MyStruct<'a> {
fn get_str(&self) -> &str { ... }
// 自动推断,不需要写生命周期
}
'static 生命周期'static 表示跟程序一样长的生命周期。
let s: &'static str = "hello"; // 字符串字面量
字符串字面量存在二进制文件里,程序结束才释放,所以是 'static。
如果结构体包含引用字段,需要标注生命周期:
struct ImportantExcerpt<'a> {
part: &'a str,
}
fn main() {
let novel = String::from("Call me Ishmael. Some years ago...");
let first_sentence = novel.split('.').next().unwrap();
let excerpt = ImportantExcerpt {
part: first_sentence,
};
println!("摘录:{}", excerpt.part);
}
含义:ImportantExcerpt 实例不能比它引用的 novel 活得更久。
// 返回两个字符串中较长的那个
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() {
x
} else {
y
}
}
fn main() {
let s1 = String::from("hello");
let s2 = String::from("world!");
let result = longest(&s1, &s2);
println!("较长的是:{}", result);
}
fn get_invalid() -> &str {
let s = String::from("hello");
&s // ❌ 错误!s 在函数结束时被 drop
}
编译器错误:
error[E0106]: missing lifetime specifier
--> src/main.rs:1:18
|
1 | fn get_invalid() -> &str {
| ^ expected named lifetime parameter
|
= help: this function's return type contains a borrowed value,
but there is no value for it to be borrowed from
人话翻译:编译器:"你返回的引用指向谁?s 是局部变量,函数结束就没了。你想返回个'过期引用'?没门!"
修复:返回 String 而不是引用。
fn get_valid() -> String {
let s = String::from("hello");
s // 返回所有权
}
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() {
x
} else {
y
}
}
fn main() {
let s1 = String::from("hello");
let result;
{
let s2 = String::from("world!");
result = longest(&s1, &s2); // s2 生命周期短
} // s2 在这里结束
println!("{}", result); // ❌ 错误!result 可能引用已释放的 s2
}
编译器错误:
error[E0597]: `s2` does not live long enough
--> src/main.rs:12:32
|
11 | let s2 = String::from("world!");
| -- binding `s2` declared here
12 | result = longest(&s1, &s2);
| ^^ borrowed value does not live long enough
13 | }
| - `s2` dropped here while still borrowed
人话翻译:编译器:"result 的生命周期跟 s2 绑定,但 s2 在内层作用域就结束了。你想让 result 引用一个已经释放的值?不行!"
修复:让 s2 活得跟 result 一样久。
fn main() {
let s1 = String::from("hello");
let s2 = String::from("world!");
let result = longest(&s1, &s2);
println!("{}", result);
}
struct Excerpt {
part: &str, // ❌ 错误!缺少生命周期
}
编译器错误:
error[E0106]: missing lifetime specifier
--> src/main.rs:2:11
|
2 | part: &str,
| ^ expected named lifetime parameter
|
help: consider introducing a named lifetime parameter
|
1 | struct Excerpt<'a> {
2 | part: &'a str,
修复:
struct Excerpt<'a> {
part: &'a str,
}
// 不需要!编译器能推断
fn first_word(s: &str) -> &str {
let bytes = s.as_bytes();
for (i, &item) in bytes.iter().enumerate() {
if item == b' ' {
return &s[..i];
}
}
&s[..]
}
原则:先不写,编译器报错再加。
fn get_ref(x: &i32) -> &i32 {
x // 不需要生命周期注解
}
单个引用参数,编译器能推断。
'static 滥用fn process(s: &'static str) { ... }
除非你真的需要 'static(比如全局常量),否则用普通生命周期。
fn process(s: &str) { ... } // 更灵活
struct Data<'a> {
value: &'a str,
}
impl<'a> Data<'a> {
fn new(value: &str) -> Data { // ❌ 错误!缺少生命周期
Data { value }
}
}
修复:
impl<'a> Data<'a> {
fn new(value: &'a str) -> Data<'a> {
Data { value }
}
}
#[derive(Debug)]
struct HighlightedText<'a> {
text: &'a str,
start: usize,
end: usize,
}
impl<'a> HighlightedText<'a> {
fn new(text: &'a str, start: usize, end: usize) -> Self {
HighlightedText { text, start, end }
}
fn highlighted(&self) -> &str {
&self.text[self.start..self.end]
}
}
fn main() {
let text = String::from("Hello, Rust!");
let highlighted = HighlightedText::new(&text, , );
println!("原文:{}", highlighted.text);
println!("高亮:{}", highlighted.highlighted());
}
struct Pair<'a, T> {
x: &'a T,
y: &'a T,
}
impl<'a, T> Pair<'a, T> {
fn new(x: &'a T, y: &'a T) -> Self {
Pair { x, y }
}
}
fn main() {
let a = ;
let b = ;
let pair = Pair::new(&a, &b);
println!("x={}, y={}", pair.x, pair.y);
}
struct TextProcessor {
text: String,
}
impl TextProcessor {
fn first_word(&self) -> &str {
self.text.split_whitespace().next().unwrap_or("")
}
fn as_str(&self) -> &str {
&self.text
}
}
fn main() {
let processor = TextProcessor {
text: String::from("Hello, World!"),
};
println!("第一个单词:{}", processor.first_word());
println!("全文:{}", processor.as_str());
}
注意:方法里 &self 的生命周期自动赋给返回值,不需要写。

&self 时编译器能自动推断'static 表示程序级生命周期,字符串字面量是 'staticimpl 块要匹配🎉 恭喜!入门基础 10 篇完成!
你已经掌握了 Rust 的核心基础:所有权、变量、函数、模块、结构体、枚举、借用、字符串、生命周期。接下来可以进入核心概念篇,学习集合、错误处理、泛型、Trait 等进阶内容!
下篇预告:核心概念篇第 11 篇——集合类型,聊聊 Vec、HashMap 这些实用数据结构!