Rust 生命周期¶
生命周期描述引用之间的有效期关系,防止悬垂引用。标注不会延长任何值的实际寿命,也不会改变运行时行为;它只是帮助编译器验证借用关系。
何时需要标注¶
多数局部借用可由编译器推断。函数接收多个引用并返回引用时,编译器可能无法判断返回值来自哪个输入,此时要声明生命周期参数。
fn longest<'a>(left: &'a str, right: &'a str) -> &'a str {
if left.len() >= right.len() { left } else { right }
}
fn main() {
let first = String::from("abcd");
let second = String::from("xyz");
let result = longest(&first, &second);
assert_eq!(result, "abcd");
}
这里 'a 表示返回引用不会比两个输入中较短的有效期更长。它不是要求两个输入实际存活完全相同的时间。
结构体中的引用¶
结构体保存引用时必须声明引用有效期,使结构体实例不能比被引用数据活得更久。
#[derive(Debug)]
struct Excerpt<'a> {
text: &'a str,
}
impl<'a> Excerpt<'a> {
fn first_word(&self) -> &str {
self.text.split_whitespace().next().unwrap_or("")
}
}
fn main() {
let document = String::from("Rust ownership");
let excerpt = Excerpt { text: &document };
assert_eq!(excerpt.first_word(), "Rust");
}
若结构体本应独立拥有数据,使用 String 通常比给每层都添加生命周期更简单。引用字段适合视图、解析结果或明确绑定到输入的零复制数据。
生命周期省略规则¶
函数签名中的省略主要遵循三条规则:每个输入引用获得独立生命周期;只有一个输入引用时其生命周期赋给所有省略的输出引用;方法若有 &self 或 &mut self,其生命周期赋给省略的输出引用。
fn first(text: &str) -> &str { // 等价于 fn first<'a>(text: &'a str) -> &'a str
text.split(',').next().unwrap_or("")
}
struct Name(String);
impl Name {
fn as_str(&self) -> &str { &self.0 }
}
fn main() {
let name = Name(String::from("Ada"));
assert_eq!(first(name.as_str()), "Ada");
}
省略是固定规则,不是编译器凭函数体猜测。规则无法确定输出来源时,必须显式标注或重新设计返回所有权。
'static 的正确理解¶
&'static str 表示引用在整个程序运行期间有效,字符串字面量就是典型例子。类型约束 T: 'static 表示 T 不包含生命周期短于 'static 的借用数据;它不表示该值永远存在,也不表示一定是全局变量。
拥有所有权的 String 通常满足 T: 'static,但它仍会在所有者离开作用域时析构。不要用泄漏内存的方式制造 'static 引用来绕过设计问题。
常见错误与工程注意¶
- 不能返回局部
String的&str,因为函数结束后数据被释放;应返回String或借用输入。 - 给所有引用都写同一个
'a可能建立过强关系,导致借用被不必要地延长。 'static不是“活得很久”的模糊标签;线程或异步任务要求它时,通常意味着任务不能借用短期栈数据。- 生命周期错误常源于所有权边界不清;先判断应该拥有、共享还是临时查看数据,再调整标注。
- 自引用结构体在安全 Rust 中较难直接表达,因为移动结构体会改变地址;通常改用索引、拥有数据或专门抽象。
- 生命周期标注不能修复真实悬垂引用,只能描述本来就安全的关系。