第7章 生命周期
7.1 为什么需要生命周期标注
7.1.1 悬垂引用问题
fn main() {
let r;
{
let x = 5;
r = &x; // ❌ 错误!x 的生命周期太短
} // x 在此离开作用域
println!("r: {}", r); // r 引用的数据已失效
}
编译器报错:
error[E0597]: `x` does not live long enough
7.1.2 生命周期的本质
生命周期不是延长变量的寿命,而是描述引用之间的关系。
类比:合同有效期
- 你借了一本书(引用)
- 合同规定归还日期(生命周期)
- 合同不会让书存在更久,只是规定你能用多久
7.1.3 借用检查器
Rust 编译器的借用检查器会比较作用域来确保引用有效。
fn main() {
let r; // ---------+-- 'a
// |
{ // |
let x = 5; // -+-- 'b |
r = &x; // | |
} // -+ |
// |
println!("r: {}", r); // |
} // ---------+
'b 比 'a 短,所以引用无效。
7.2 生命周期标注语法
7.2.1 语法规则
生命周期参数以撇号 ' 开头,通常使用小写字母。
&i32 // 引用
&'a i32 // 带有显式生命周期的引用
&'a mut i32 // 带有显式生命周期的可变引用
常见的生命周期名称:
'a,'b,'c- 通用生命周期'static- 特殊的静态生命周期
7.2.2 单个生命周期标注没有意义
fn foo<'a>(x: &'a i32) {
// 单个生命周期标注不提供额外信息
}
生命周期标注的意义在于描述多个引用之间的关系。
7.3 函数签名中的生命周期
7.3.1 问题场景
fn longest(x: &str, y: &str) -> &str {
if x.len() > y.len() {
x
} else {
y
}
}
编译器报错:
error[E0106]: missing lifetime specifier
--> src/main.rs:1:33
|
1 | fn longest(x: &str, y: &str) -> &str {
| ---- ---- ^ expected named lifetime parameter
问题:编译器不知道返回的引用是 x 还是 y,无法确定返回值的生命周期。
7.3.2 添加生命周期标注
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() {
x
} else {
y
}
}
fn main() {
let string1 = String::from("long string is long");
let string2 = String::from("xyz");
let result = longest(string1.as_str(), string2.as_str());
println!("最长的字符串是 {}", result);
}
含义:
- 参数
x和y的生命周期都是'a - 返回值的生命周期也是
'a 'a的实际生命周期是x和y生命周期中较短的那个
7.3.3 生命周期约束示例
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() {
x
} else {
y
}
}
fn main() {
let string1 = String::from("long string is long");
{
let string2 = String::from("xyz");
let result = longest(string1.as_str(), string2.as_str());
println!("最长的字符串是 {}", result); // ✅ 正确
}
}
错误示例:
fn main() {
let string1 = String::from("long string is long");
let result;
{
let string2 = String::from("xyz");
result = longest(string1.as_str(), string2.as_str());
} // string2 在此离开作用域
println!("最长的字符串是 {}", result); // ❌ 错误!
}
7.3.4 返回值不依赖参数的情况
fn longest<'a>(x: &str, y: &str) -> &'a str {
let result = String::from("really long string");
result.as_str() // ❌ 错误!返回局部变量的引用
}
正确做法:返回所有权
fn longest(x: &str, y: &str) -> String {
String::from("really long string")
}
7.4 结构体中的生命周期
7.4.1 结构体持有引用
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,
};
println!("摘录:{}", i.part);
}
含义:ImportantExcerpt 实例的生命周期不能超过 part 字段引用的数据的生命周期。
7.4.2 错误示例
struct ImportantExcerpt<'a> {
part: &'a str,
}
fn main() {
let i;
{
let novel = String::from("Call me Ishmael.");
let first_sentence = novel.split('.').next().unwrap();
i = ImportantExcerpt {
part: first_sentence,
};
} // novel 在此离开作用域
println!("{}", i.part); // ❌ 错误!
}
7.4.3 结构体方法中的生命周期
struct ImportantExcerpt<'a> {
part: &'a str,
}
impl<'a> ImportantExcerpt<'a> {
fn level(&self) -> i32 {
3
}
fn announce_and_return_part(&self, announcement: &str) -> &str {
println!("注意!{}", announcement);
self.part
}
}
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,
};
println!("级别:{}", i.level());
println!("部分:{}", i.announce_and_return_part("听好了"));
}
7.5 生命周期省略规则(Elision Rules)
编译器使用三条规则来推断生命周期,如果应用这些规则后仍有歧义,则需要显式标注。
7.5.1 规则 1:每个引用参数都有自己的生命周期
fn foo(x: &i32) // 推断为 fn foo<'a>(x: &'a i32)
fn foo(x: &i32, y: &i32) // 推断为 fn foo<'a, 'b>(x: &'a i32, y: &'b i32)
7.5.2 规则 2:如果只有一个输入生命周期,赋给所有输出生命周期
fn foo(x: &i32) -> &i32 // 推断为 fn foo<'a>(x: &'a i32) -> &'a i32
7.5.3 规则 3:如果有多个输入生命周期,但其中一个是 &self 或 &mut self,self 的生命周期赋给所有输出生命周期
impl<'a> ImportantExcerpt<'a> {
fn announce_and_return_part(&self, announcement: &str) -> &str {
// 推断为:
// fn announce_and_return_part<'a, 'b>(&'a self, announcement: &'b str) -> &'a str
self.part
}
}
7.5.4 需要显式标注的情况
// 规则无法推断,需要显式标注
fn longest(x: &str, y: &str) -> &str { // ❌ 错误
// ...
}
// 显式标注
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
// ...
}
7.6 'static 生命周期
'static 表示引用在整个程序运行期间都有效。
7.6.1 字符串字面量
fn main() {
let s: &'static str = "I have a static lifetime.";
println!("{}", s);
}
所有字符串字面量都有 'static 生命周期,因为它们存储在程序的二进制文件中。
7.6.2 静态变量
static LANGUAGE: &str = "Rust";
fn main() {
println!("语言:{}", LANGUAGE);
}
7.6.3 何时使用 'static
不要滥用 'static:
// ❌ 不好的做法
fn get_string() -> &'static str {
let s = String::from("hello");
// s.as_str() // 错误!不能返回局部变量的引用
"hello" // 只能返回字符串字面量
}
// ✅ 好的做法
fn get_string() -> String {
String::from("hello")
}
7.7 泛型 + Trait + 生命周期的综合运用
7.7.1 综合示例
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!("公告!{}", ann);
if x.len() > y.len() {
x
} else {
y
}
}
fn main() {
let string1 = String::from("abcd");
let string2 = "xyz";
let result = longest_with_an_announcement(
string1.as_str(),
string2,
"今天是特别的一天",
);
println!("最长的字符串是 {}", result);
}
解析:
'a:生命周期参数T:泛型类型参数T: Display:Trait Bound
7.7.2 复杂示例
use std::fmt::Display;
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 {
Self { x, y }
}
}
impl<'a, T: Display + PartialOrd> Pair<'a, T> {
fn cmp_display(&self) {
if self.x >= self.y {
println!("最大值是 x = {}", self.x);
} else {
println!("最大值是 y = {}", self.y);
}
}
}
fn main() {
let x = 10;
let y = 20;
let pair = Pair::new(&x, &y);
pair.cmp_display();
}
7.8 实战:实现一个泛型缓存结构
项目需求:
- 缓存计算结果
- 支持泛型类型
- 使用生命周期确保安全
实现代码:
use std::collections::HashMap;
use std::hash::Hash;
struct Cacher<'a, T, U>
where
T: Fn(&U) -> U,
U: Clone + Hash + Eq,
{
calculation: T,
values: HashMap<&'a U, U>,
}
impl<'a, T, U> Cacher<'a, T, U>
where
T: Fn(&U) -> U,
U: Clone + Hash + Eq,
{
fn new(calculation: T) -> Cacher<'a, T, U> {
Cacher {
calculation,
values: HashMap::new(),
}
}
fn value(&mut self, arg: &'a U) -> U {
match self.values.get(arg) {
Some(v) => v.clone(),
None => {
let v = (self.calculation)(arg);
self.values.insert(arg, v.clone());
v
}
}
}
}
fn main() {
let mut expensive_closure = Cacher::new(|num: &u32| {
println!("计算中...");
std::thread::sleep(std::time::Duration::from_secs(1));
num * 2
});
let arg1 = 10;
let arg2 = 20;
println!("第一次调用:{}", expensive_closure.value(&arg1));
println!("第二次调用(缓存):{}", expensive_closure.value(&arg1));
println!("不同参数:{}", expensive_closure.value(&arg2));
}
简化版本(不使用生命周期):
use std::collections::HashMap;
struct Cacher<T, U>
where
T: Fn(U) -> U,
U: Clone + Hash + Eq,
{
calculation: T,
values: HashMap<U, U>,
}
impl<T, U> Cacher<T, U>
where
T: Fn(U) -> U,
U: Clone + Hash + Eq,
{
fn new(calculation: T) -> Cacher<T, U> {
Cacher {
calculation,
values: HashMap::new(),
}
}
fn value(&mut self, arg: U) -> U {
match self.values.get(&arg) {
Some(v) => v.clone(),
None => {
let v = (self.calculation)(arg.clone());
self.values.insert(arg, v.clone());
v
}
}
}
}
fn main() {
let mut expensive_closure = Cacher::new(|num: u32| {
println!("计算中...");
std::thread::sleep(std::time::Duration::from_secs(1));
num * 2
});
println!("第一次调用:{}", expensive_closure.value(10));
println!("第二次调用(缓存):{}", expensive_closure.value(10));
println!("不同参数:{}", expensive_closure.value(20));
}
常见误区与陷阱
误区 1:以为生命周期会改变引用的实际生命周期
// ❌ 错误理解
// 生命周期标注会让 x 活得更久
// ✅ 正确理解
// 生命周期标注只是告诉编译器引用之间的关系
// 不会改变任何值的实际生命周期
误区 2:过度使用 'static
// ❌ 不好
fn get_str() -> &'static str {
// 只能返回字符串字面量
"hello"
}
// ✅ 更好
fn get_str() -> String {
String::from("hello")
}
误区 3:混淆生命周期和作用域
// 生命周期:引用有效的时间范围
// 作用域:变量存在的代码范围
fn main() {
let r; // r 的作用域开始
{
let x = 5; // x 的作用域开始
r = &x; // r 引用 x
} // x 的作用域结束,x 被释放
// r 的生命周期在此失效(引用的数据已释放)
// println!("{}", r); // 错误!
} // r 的作用域结束
实战练习
练习 7.1:为函数添加生命周期标注
以下函数缺少生命周期标注,请添加正确的生命周期使其编译通过:
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
}
}
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);
}
}
输出:
最长的字符串是:long string
说明:编译器无法确定返回值引用的是 x 还是 y,因此需要生命周期标注 'a 来告诉编译器:返回值的生命周期是两个参数生命周期中较短的那个。
练习 7.2:结构体生命周期
定义一个结构体 Book,包含标题和作者的引用,实现方法返回完整信息。
参考答案:
struct Book<'a> {
title: &'a str,
author: &'a str,
}
impl<'a> Book<'a> {
fn new(title: &'a str, author: &'a str) -> Book<'a> {
Book { title, author }
}
fn info(&self) -> String {
format!("《{}》 —— {}", self.title, self.author)
}
fn title(&self) -> &str {
self.title
}
}
fn main() {
let title = String::from("Rust 程序设计");
let author = String::from("Steve Klabnik");
let book = Book::new(&title, &author);
println!("{}", book.info());
println!("书名:{}", book.title());
}
输出:
《Rust 程序设计》 —— Steve Klabnik
书名:Rust 程序设计
说明:Book 结构体持有引用,生命周期参数 'a 确保结构体实例不会比它引用的数据活得更久。
练习 7.3:多个生命周期
编写函数,接受两个不同生命周期的引用,返回其中一个。
参考答案:
fn first_or_default<'a, 'b>(first: &'a str, default: &'b str) -> &'a str
where
'b: 'a,
{
if first.is_empty() {
default
} else {
first
}
}
fn main() {
let default = "默认值";
let value1 = String::from("hello");
let result1 = first_or_default(&value1, default);
println!("result1: {}", result1);
let value2 = String::from("");
let result2 = first_or_default(&value2, default);
println!("result2: {}", result2);
}
输出:
result1: hello
result2: 默认值
说明:'b: 'a 表示 'b 至少和 'a 一样长。这样当 first 为空时,返回 default 是安全的,因为 default 的生命周期不短于返回值要求的生命周期。
练习 7.4:综合应用
实现一个泛型结构体,持有引用,并实现带有 Trait Bound 的方法。
参考答案:
use std::fmt::Display;
struct Wrapper<'a, T: Display> {
value: &'a T,
label: &'a str,
}
impl<'a, T: Display> Wrapper<'a, T> {
fn new(value: &'a T, label: &'a str) -> Wrapper<'a, T> {
Wrapper { value, label }
}
fn display(&self) {
println!("[{}]: {}", self.label, self.value);
}
}
impl<'a, T: Display + PartialOrd> Wrapper<'a, T> {
fn is_greater_than(&self, other: &T) -> bool {
self.value > other
}
}
fn main() {
let num = 42;
let w1 = Wrapper::new(&num, "数字");
w1.display();
println!("42 > 30? {}", w1.is_greater_than(&30));
let text = String::from("hello");
let w2 = Wrapper::new(&text, "文本");
w2.display();
let pi = 3.14;
let w3 = Wrapper::new(&pi, "圆周率");
w3.display();
println!("3.14 > 3.0? {}", w3.is_greater_than(&3.0));
}
输出:
[数字]: 42
42 > 30? true
[文本]: hello
[圆周率]: 3.14
3.14 > 3.0? true
说明:这个例子综合运用了泛型参数 T、生命周期参数 'a、Trait Bound(Display、PartialOrd),以及有条件地为特定类型实现方法。
本章小结
- 生命周期的本质:描述引用之间的关系,不改变实际生命周期
- 生命周期标注:使用
'a等符号标注引用的生命周期参数 - 函数签名:当返回引用时,需要标注生命周期关系
- 结构体:持有引用的结构体需要生命周期参数
- 省略规则:编译器可以推断大部分情况,减少显式标注
- 'static:整个程序运行期间有效,不要滥用
- 综合运用:生命周期、泛型、Trait 可以组合使用
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)