生命周期
生命周期(Lifetime)是 Rust 中引用保持有效的作用域。
为什么需要生命周期?
生命周期防止悬垂引用(dangling references):
fn main() {
let r; // ---------+-- 'a
// |
{ // |
let x = 5; // -+-- 'b |
r = &x; // | |
} // -+ |
// |
println!("r: {}", r); // | ❌ 编译错误!
} // ---------+
错误原因:x 的生命周期 'b 比 r 的生命周期 'a 短,当尝试使用 r 时,x
已被释放。
生命周期注解语法
生命周期注解使用单引号开头,通常使用小写字母:
&i32 // 普通引用
&'a i32 // 带有显式生命周期的引用
&'a mut i32 // 带有显式生命周期的可变引用
函数中的生命周期
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("abcd");
let string2 = "xyz";
let result = longest(string1.as_str(), string2);
println!("最长的字符串是: {}", result);
}
含义:返回值的生命周期与参数中生命周期较短的那个相同。
Loading diagram...
生命周期省略规则
编译器可以在某些情况下自动推导生命周期,无需显式标注。
三条省略规则:
- 规则 1:每个引用参数都有自己的生命周期
fn foo(x: &i32) // 实际是 fn foo<'a>(x: &'a i32)
- 规则 2:如果只有一个输入生命周期,赋予所有输出
fn foo(x: &i32) -> &i32 // 实际是 fn foo<'a>(x: &'a i32) -> &'a i32
- 规则 3:如果有
&self或&mut self,返回值的生命周期是self的生命周期
impl MyStruct {
fn get_data(&self) -> &str { // 返回值生命周期等于 self
&self.data
}
}
需要显式标注的情况
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 }
}
结构体中的生命周期
结构体包含引用时,需要生命周期注解:
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("找不到 '.'");
let excerpt = ImportantExcerpt {
part: first_sentence,
};
println!("{}", excerpt.part);
}
含义:ImportantExcerpt 实例的生命周期不能超过 part 字段引用的数据。
方法中的生命周期
impl<'a> ImportantExcerpt<'a> {
fn level(&self) -> i32 { // 省略规则 3:返回值生命周期来自 self
3
}
fn announce_and_return_part(&self, announcement: &str) -> &str {
println!("请注意: {}", announcement);
self.part // 返回值生命周期来自 self
}
}
静态生命周期
'static 生命周期表示整个程序运行期间:
let s: &'static str = "我有一个静态生命周期"; // 字符串字面量
字符串字面量默认是 'static,因为它们被存储在程序的二进制文件中。
谨慎使用 'static:
// ❌ 不推荐:过度使用 'static
fn get_str() -> &'static str {
"hello"
}
// ✅ 更好:返回 String(有所有权)
fn get_string() -> String {
String::from("hello")
}
生命周期约束
可以指定生命周期之间的关系:
fn longest_with_constraint<'a, 'b: 'a>(x: &'a str, y: &'b str) -> &'a str {
// 'b: 'a 表示 'b 的生命周期至少与 'a 一样长
x
}
多个生命周期参数
fn compare<'a, 'b>(x: &'a str, y: &'b str) -> &'a str {
x // 只能返回 x,不能返回 y(生命周期不同)
}
fn main() {
let str1 = String::from("long string is long");
let result;
{
let str2 = String::from("xyz");
result = compare(str1.as_str(), str2.as_str());
} // str2 被释放,但不影响 result(它来自 str1)
println!("{}", result); // ✅ 正常工作
}
生命周期与泛型
结合泛型和生命周期:
use std::fmt::Display;
fn longest_with_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
}
}
实践示例
示例 1:解析配置
struct Config<'a> {
host: &'a str,
port: u16,
}
impl<'a> Config<'a> {
fn new(host: &'a str, port: u16) -> Self {
Config { host, port }
}
fn connection_string(&self) -> String {
format!("{}:{}", self.host, self.port)
}
}
fn main() {
let host = String::from("localhost");
let config = Config::new(&host, 8080);
println!("连接到: {}", config.connection_string());
} // host 和 config 同时被释放,生命周期正确
示例 2:迭代器实现
struct StrSplit<'a> {
remainder: &'a str,
delimiter: &'a str,
}
impl<'a> StrSplit<'a> {
fn new(haystack: &'a str, delimiter: &'a str) -> Self {
StrSplit {
remainder: haystack,
delimiter,
}
}
}
impl<'a> Iterator for StrSplit<'a> {
type Item = &'a str;
fn next(&mut self) -> Option<Self::Item> {
if let Some(next_delim) = self.remainder.find(self.delimiter) {
let until_delimiter = &self.remainder[..next_delim];
self.remainder = &self.remainder[next_delim + self.delimiter.len()..];
Some(until_delimiter)
} else if self.remainder.is_empty() {
None
} else {
let rest = self.remainder;
self.remainder = "";
Some(rest)
}
}
}
fn main() {
let haystack = "a,b,c";
let letters: Vec<_> = StrSplit::new(haystack, ",").collect();
println!("{:?}", letters); // ["a", "b", "c"]
}
常见错误与修复
错误 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 }
}
错误 2:返回局部变量的引用
fn get_str() -> &str { // ❌ 编译错误
let s = String::from("hello");
&s // s 在函数结束时被释放
}
// 修复:返回 String(转移所有权)
fn get_string() -> String { // ✅
String::from("hello")
}
错误 3:结构体缺少生命周期注解
struct Excerpt {
part: &str, // ❌ 编译错误:缺少生命周期
}
// 修复:添加生命周期
struct Excerpt<'a> {
part: &'a str, // ✅
}
实践建议
✅ 推荐做法
// 1. 优先使用生命周期省略规则
fn first_word(s: &str) -> &str { // 自动推导
&s[..1]
}
// 2. 只在必要时显式标注
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() { x } else { y }
}
// 3. 使用有意义的生命周期名称
fn parse_config<'cfg>(input: &'cfg str) -> Config<'cfg> {
// 'cfg 比 'a 更具描述性
Config { data: input }
}
struct Config<'cfg> {
data: &'cfg str,
}
❌ 避免做法
// ❌ 过度使用 'static
fn get_data() -> &'static str {
"data" // 只有在确实是全局常量时才使用
}
// ❌ 不必要的复杂生命周期
fn simple<'a, 'b, 'c>(x: &'a str, y: &'b str) -> &'c str {
// 如果可以简化,就简化
x
}
// ❌ 返回局部变量的引用
fn bad() -> &str {
let s = String::from("bad");
&s // 悬垂引用
}
练习题
练习 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 }
}
练习 2:结构体生命周期
实现一个包含字符串切片的结构体:
struct Book {
title: &str,
author: &str,
}
查看答案
struct Book<'a> {
title: &'a str,
author: &'a str,
}
fn main() {
let title = String::from("Rust Programming");
let author = String::from("Graydon Hoare");
let book = Book {
title: &title,
author: &author,
};
println!("{} by {}", book.title, book.author);
}
小结
- ✅ 生命周期确保引用始终有效
- ✅ 使用
'a语法标注生命周期 - ✅ 编译器可以通过省略规则自动推导生命周期
- ✅ 结构体包含引用时需要生命周期注解
- ✅
'static表示整个程序生命周期 - ✅ 生命周期在编译时检查,零运行时开销
下一步,我们将学习模式匹配。