14 rust基础 -lifetimes(生命周期)
Rust 生命周期(Lifetimes)学习总结
1️⃣ 生命周期的基本概念
Rust 的生命周期是一种保证引用有效性的机制,用来防止悬垂引用和数据竞争。
- 生命周期标注是告诉编译器不同引用之间的关系,以确保引用不会比其引用的数据活得更久。
- 生命周期不会改变数据的生存时间,而是检查引用的使用是否合法。
示例:
fn main() {
let r;
{
let x = 5;
r = &x; // 报错:x 在 r 之前被销毁
}
println!("r: {}", r);
}
编译器报错,因为 x
的生命周期比 r
短。
2️⃣ 生命周期标注语法
- 生命周期标注使用
'a
这样的标识符。 - 语法:
&'a T
表示引用的生命周期是'a
。
示例:
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() { x } else { y }
}
解释:
- 输入的两个引用
x
和y
的生命周期都是'a
。 - 返回值的引用也拥有生命周期
'a
。 - 编译器知道返回的引用至少活得和输入的引用一样长。
3️⃣ 函数签名中的生命周期
- 生命周期标注主要出现在函数签名中,用于描述参数和返回值之间的依赖关系。
- 生命周期标注不会影响运行时行为,只是编译期检查工具。
示例:
fn first_word<'a>(s: &'a str) -> &'a str {
let bytes = s.as_bytes();
for (i, &item) in bytes.iter().enumerate() {
if item == b' ' { return &s[0..i]; }
}
s
}
这里的 'a
确保返回值不会比输入字符串 s
活得更久。
4️⃣ 生命周期省略规则(Lifetime Elision Rules)
在某些情况下,Rust 可以推断生命周期,无需显式标注。
编译器应用以下三条规则来自动插入生命周期标注:
- 每个引用参数都有自己的生命周期参数。
- 如果只有一个输入生命周期,则返回值的生命周期等于输入生命周期。
- 如果有多个输入生命周期,但其中一个是
self
或&self
,那么返回值的生命周期等于self
。
示例:
fn foo(x: &str) -> &str { x } // 自动推断为 fn foo<'a>(x: &'a str) -> &'a str
5️⃣ 结构体中的生命周期
当结构体中包含引用时,必须为引用指定生命周期:
struct Book<'a> {
title: &'a str,
author: &'a str,
}
生命周期标注确保 Book
实例不会比它内部的引用活得更久。
使用示例:
fn main() {
let title = String::from("1984");
let author = String::from("George Orwell");
let book = Book { title: &title, author: &author };
println!("{} by {}", book.title, book.author);
}
6️⃣ 静态生命周期
'static
表示引用可以活得跟程序一样久。- 常见于字符串字面量,因为它们被嵌入到程序的二进制文件中:
let s: &'static str = "I have a static lifetime.";
- 但要小心使用
'static
生命周期标注,不要让不必要的引用活得过长。
7️⃣ 生命周期与特征对象
在使用特征对象时,生命周期也经常出现,比如:
trait Trait {}
struct Foo<'a> {
part: &'a str,
}
impl<'a> Trait for Foo<'a> {}
fn do_something(x: &dyn Trait) {}
在复杂的泛型和特征对象中,生命周期标注帮助编译器确保引用的安全性。
🎯 总结
- 生命周期标注是编译期概念,保障引用不会变成悬垂引用。
- 常用生命周期:
'a
,'static
- 函数签名中的生命周期: 说明参数和返回值之间的生命周期关系。
- 结构体中的生命周期: 当结构体包含引用时必须使用生命周期标注。
- 生命周期省略规则: 简化代码,自动推断生命周期。
- 'static 生命周期: 引用在程序运行期间一直有效。