生命周期
生命周期是 Rust 确保引用始终有效的机制。它让编译器能够在编译时检查引用是否有效,避免悬垂引用。
什么是生命周期?
问题引入
考虑以下代码:
fn main() {
let r; // ---------+-- 'a
// |
{ // |
let x = 5; // -+-- 'b |
r = &x; // | |
} // -+ |
// |
println!("r: {}", r); // |
} // ---------+
问题:x 在内部作用域结束时被释放,但 r 仍然引用它,这会导致悬垂引用。
Rust 编译器会拒绝这段代码,因为它检测到 r 的生命周期比 x 长。
生命周期标注
生命周期标注不会改变引用的生命周期长度,它们只是描述多个引用之间的关系:
&i32 // 引用
&'a i32 // 带有显式生命周期的引用
&'a mut i32 // 带有显式生命周期的可变引用
函数中的生命周期
基本示例
// 错误示例:编译器不知道返回的引用来自哪个参数
// 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("长字符串");
{
let string2 = String::from("xyz");
let result = longest(&string1, &string2);
println!("最长的字符串: {}", result);
}
}
解释:
'a是生命周期参数x: &'a str表示 x 的生命周期至少为 'ay: &'a str表示 y 的生命周期至少为 'a-> &'a str表示返回值的生命周期也为 'a- 返回值的生命周期与参数中较短的那个一致
生命周期规则
编译器使用三条规则推断生命周期:
-
每个引用参数都获得一个生命周期:
fn foo(x: &i32)→fn foo<'a>(x: &'a i32)
-
如果只有一个输入生命周期,它被赋给所有输出生命周期:
fn foo(x: &str) -> &str→fn foo<'a>(x: &'a str) -> &'a str
-
如果有多个输入生命周期但其中一个是 &self 或 &mut self,self 的生命周期被赋给所有输出生命周期:
- 方法中的常见情况
示例分析
// 规则 1 和 2 生效:无需显式标注
fn first_word(s: &str) -> &str {
let bytes = s.as_bytes();
for (i, &item) in bytes.iter().enumerate() {
if item == b' ' {
return &s[0..i];
}
}
&s[..]
}
// 需要显式标注:多个输入生命周期
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() { x } else { y }
}
// 返回值不一定来自参数
fn longest_x<'a>(x: &'a str, y: &str) -> &'a str {
x // 返回值只与 x 相关
}
// 错误:不能返回局部变量的引用
// fn invalid<'a>(x: &str, y: &str) -> &'a str {
// let result = String::from("hello");
// &result // result 在函数结束时被释放
// }
结构体中的生命周期
基本用法
// 结构体持有引用时需要生命周期标注
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持有一个字符串引用 part: &'a str表示引用的生命周期必须至少与结构体一样长- 结构体实例不能比它持有的引用存活更久
结构体方法
struct ImportantExcerpt<'a> {
part: &'a str,
}
impl<'a> ImportantExcerpt<'a> {
// 规则 3 生效:self 的生命周期传递给返回值
fn level(&self) -> i32 {
3
}
// 返回值生命周期与 self 相同
fn announce_and_return_part(&self, announcement: &str) -> &str {
println!("请注意: {}", announcement);
self.part
}
// 多生命周期示例
fn compare_parts<'b>(&self, other: &'b str) -> &'a str {
if self.part.len() > other.len() {
self.part
} else {
self.part // 返回 self.part,生命周期为 'a
}
}
}
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.level());
println!("部分: {}", excerpt.announce_and_return_part("公告"));
}
多个生命周期
struct Context<'s, 'c> {
text: &'s str,
comment: &'c str,
}
impl<'s, 'c> Context<'s, 'c> {
fn get_text(&self) -> &'s str {
self.text
}
fn get_comment(&self) -> &'c str {
self.comment
}
}
fn main() {
let text = String::from("主要内容");
{
let comment = String::from("这是一条评论");
let ctx = Context {
text: &text,
comment: &comment,
};
println!("{}", ctx.get_text());
println!("{}", ctx.get_comment());
}
}
生命周期省略
历史上,所有引用都需要显式生命周期标注。现在,编译器可以自动推断常见模式:
// 省略前
fn first_word<'a>(s: &'a str) -> &'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[0..i];
}
}
&s[..]
}
// 另一个省略示例
fn longest_string(x: &str, y: &str) -> String {
if x.len() > y.len() {
x.to_string()
} else {
y.to_string()
}
}
静态生命周期
'static 表示整个程序运行期间都有效的生命周期:
// 字符串字面量具有 'static 生命周期
let s: &'static str = "我有静态生命周期";
// 可以作为默认生命周期
fn print_str(s: &'static str) {
println!("{}", s);
}
fn main() {
let s1: &'static str = "hello";
print_str(s1);
// 错误:String 不能转换为 &'static str
// let s2 = String::from("world");
// print_str(&s2);
}
注意:
- 不要轻易使用
'static来解决生命周期问题 - 它通常用于:
- 字符串字面量
- 全局数据
- 线程间共享的数据
生命周期子类型化
生命周期可以有包含关系:
// 'a: 'b 表示 'a 至少和 'b 一样长
fn choose_first<'a, 'b>(first: &'a i32, second: &'b i32) -> &'a i32
where
'a: 'b, // 'a 包含 'b
{
first
}
fn main() {
let first = 10; // 'a
let longer;
{
let second = 20; // 'b
longer = choose_first(&first, &second);
println!("{}", longer);
}
println!("{}", longer);
}
协变和逆变
生命周期具有协变性:
fn main() {
// 长生命周期可以赋值给短生命周期
let x: &'static str = "hello";
let y: &str = x; // 'static 可以转换为任意 'a
// 但反过来不行
// let z: &'static str = y; // 错误!
}
实际应用示例
解析器
struct Parser<'a> {
input: &'a str,
}
impl<'a> Parser<'a> {
fn new(input: &'a str) -> Self {
Parser { input }
}
fn peek(&self) -> Option<char> {
self.input.chars().next()
}
fn consume(&mut self) -> Option<char> {
let ch = self.input.chars().next()?;
self.input = &self.input[ch.len_utf8()..];
Some(ch)
}
fn parse_identifier(&mut self) -> &'a str {
let start = self.input;
while let Some(ch) = self.peek() {
if ch.is_alphanumeric() || ch == '_' {
self.consume();
} else {
break;
}
}
&start[..start.len() - self.input.len()]
}
}
fn main() {
let code = "hello_world = 42";
let mut parser = Parser::new(code);
let ident = parser.parse_identifier();
println!("标识符: {}", ident);
}
文本处理
struct TextProcessor<'a> {
text: &'a str,
words: Vec<&'a str>,
}
impl<'a> TextProcessor<'a> {
fn new(text: &'a str) -> Self {
let words: Vec<&str> = text.split_whitespace().collect();
TextProcessor { text, words }
}
fn find_word(&self, word: &str) -> Option<&'a str> {
self.words.iter().find(|&&w| w == word).copied()
}
fn all_words(&self) -> &[&'a str] {
&self.words
}
}
fn main() {
let text = "Rust 是一门系统编程语言";
let processor = TextProcessor::new(text);
if let Some(word) = processor.find_word("Rust") {
println!("找到: {}", word);
}
println!("所有单词: {:?}", processor.all_words());
}
常见错误和解决方案
错误 1:生命周期不匹配
// 错误
fn bad_example<'a>(x: &str, y: &str) -> &'a str {
if x.len() > y.len() { x } else { y }
}
// 正确
fn good_example<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() { x } else { y }
}
错误 2:返回局部引用
// 错误
fn create_string() -> &str {
let s = String::from("hello");
&s // s 在函数结束时被释放
}
// 正确方案 1:返回所有权
fn create_string_owned() -> String {
String::from("hello")
}
// 正确方案 2:传入可变引用
fn create_string_to<'a>(output: &'a mut String) -> &'a str {
*output = String::from("hello");
output
}
错误 3:结构体生命周期
// 错误:结构体比引用活得更长
fn bad_struct() {
let excerpt;
{
let text = String::from("hello");
excerpt = ImportantExcerpt { part: &text };
} // text 被释放
// println!("{}", excerpt.part); // 悬垂引用!
}
// 正确:确保引用比结构体活得长
fn good_struct() {
let text = String::from("hello");
let excerpt = ImportantExcerpt { part: &text };
println!("{}", excerpt.part);
}
小结
本章我们学习了:
- 生命周期概念:确保引用始终有效
- 生命周期标注:
'a语法和用途 - 函数生命周期:参数和返回值的关联
- 结构体生命周期:持有引用的结构体
- 生命周期省略:编译器自动推断规则
- 静态生命周期:
'static的含义和用途
练习
- 编写一个函数,接受两个字符串切片,返回其中较长的那个
- 创建一个结构体,存储字符串切片,并实现相关方法
- 解释为什么以下代码无法编译:
fn main() {
let r;
{
let x = 5;
r = &x;
}
println!("{}", r);
} - 实现一个简单的文本解析器,使用生命周期确保安全性