Rust 生命周期
为什么需要标注、`'a` 怎么写、省略规则、以及 `'static`。
Key Points生命周期标注不改变存活时间,只是告诉编译器「这些引用之间的相对关系」。
为什么需要
Rust
// 编译错误:missing lifetime specifier
// 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 a = String::from("long string");
let r;
{
let b = String::from("xyz");
r = longest(&a, &b);
println!("{}", r);
}
// println!("{}", r); // 编译错误:b 已经释放,r 可能指向它
}Output
long stringThe compiler cannot tell whether the return borrows from x or y, so it cannot know how long it lives. 'a means "the return lives no longer than the shorter of the two".
生命周期不改变寿命
Rust
// 这个标注毫无作用:'a 只约束关系,不延长任何东西
fn bad<'a>(x: &'a str) -> &'a str {
x
}
// 这个编译不过:返回值引用了局部变量
// fn worse<'a>() -> &'a str {
// let s = String::from("x");
// &s
// }Many assume adding 'a relaxes the compiler. The opposite: annotations are promises, and more promises mean fewer accepted programs. That is why most functions need none.
省略规则
Rust
// 下面三个签名等价,编译器自动补全
fn f1(s: &str) -> &str { s }
fn f2<'a>(s: &'a str) -> &'a str { s }
fn f3<'a>(s: &'a str) -> &'a str { s }| Rule | Meaning |
|---|---|
| 1 | 每个输入引用各得一个独立生命周期 |
| 2 | 只有一个输入引用时,输出沿用它的 |
| 3 | 有 &self / &mut self 时,输出沿用 self 的 |
Rust
// 方法里最常见:返回 &self 的一部分,不用写生命周期
struct Parser {
input: String,
}
impl Parser {
fn first_word(&self) -> &str {
self.input.split_whitespace().next().unwrap_or("")
}
}'static
Rust
// 字符串字面量就是 &'static str
let s: &'static str = "整个程序期间有效";
// 拥有所有权的类型隐含 'static
fn need_static<T: 'static>(v: T) { }
need_static(String::from("ok"));
need_static(42);
// 常见于线程
std::thread::spawn(|| {
let x: &'static str = "hello";
println!("{}", x);
});'static does not mean "leaks". String satisfies T: 'static because it contains no short-lived references, not because it never frees. That misunderstanding makes async 'static bounds look scarier than they are.结构体里的生命周期
Rust
// 结构体持有引用时必须标生命周期
struct Excerpt<'a> {
part: &'a str,
}
impl<'a> Excerpt<'a> {
fn announce(&self) -> &str {
self.part
}
}
fn main() {
let novel = String::from("从前有座山。山里有座庙。");
let first = novel.split('。').next().unwrap();
let e = Excerpt { part: first };
println!("{}", e.announce());
}Output
从前有座山If a struct must hold data long-term, store String instead of a reference. "Own it or borrow it, do not mix" is the most practical advice for avoiding lifetime hell.
为什么需要
Rust
// 编译错误:missing lifetime specifier
// 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 a = String::from("long string");
let r;
{
let b = String::from("xyz");
r = longest(&a, &b);
println!("{}", r);
}
// println!("{}", r); // 编译错误:b 已经释放,r 可能指向它
}Output
long string编译器不知道返回的引用来自 x 还是 y,所以无法判断它能活多久。'a 的含义是「返回值不比两个参数中较短的那个活得更久」。
生命周期不改变寿命
Rust
// 这个标注毫无作用:'a 只约束关系,不延长任何东西
fn bad<'a>(x: &'a str) -> &'a str {
x
}
// 这个编译不过:返回值引用了局部变量
// fn worse<'a>() -> &'a str {
// let s = String::from("x");
// &s
// }很多人以为加 'a 能让编译器「放宽」。恰恰相反:标注是承诺,标注越多,编译器能接受的情况越少。所以绝大多数函数不需要写生命周期。
省略规则
Rust
// 下面三个签名等价,编译器自动补全
fn f1(s: &str) -> &str { s }
fn f2<'a>(s: &'a str) -> &'a str { s }
fn f3<'a>(s: &'a str) -> &'a str { s }| 规则 | 含义 |
|---|---|
| 1 | 每个输入引用各得一个独立生命周期 |
| 2 | 只有一个输入引用时,输出沿用它的 |
| 3 | 有 &self / &mut self 时,输出沿用 self 的 |
Rust
// 方法里最常见:返回 &self 的一部分,不用写生命周期
struct Parser {
input: String,
}
impl Parser {
fn first_word(&self) -> &str {
self.input.split_whitespace().next().unwrap_or("")
}
}'static
Rust
// 字符串字面量就是 &'static str
let s: &'static str = "整个程序期间有效";
// 拥有所有权的类型隐含 'static
fn need_static<T: 'static>(v: T) { }
need_static(String::from("ok"));
need_static(42);
// 常见于线程
std::thread::spawn(|| {
let x: &'static str = "hello";
println!("{}", x);
});'static 不等于「泄漏」。String 满足 T: 'static 是因为它不包含任何短命引用,不是因为它永不释放。这个误解让很多人以为异步代码里的 'static 要求很可怕。结构体里的生命周期
Rust
// 结构体持有引用时必须标生命周期
struct Excerpt<'a> {
part: &'a str,
}
impl<'a> Excerpt<'a> {
fn announce(&self) -> &str {
self.part
}
}
fn main() {
let novel = String::from("从前有座山。山里有座庙。");
let first = novel.split('。').next().unwrap();
let e = Excerpt { part: first };
println!("{}", e.announce());
}Output
从前有座山如果结构体需要长期持有,就别存引用,存 String。「要么拥有,要么借用,别混着来」——这是避免生命周期地狱最实用的一条建议。