09
生命周期 Lifetime:引用的保质期
Lifetimes · Borrow Checker · 'a
生命周期是 Rust 独有的概念,也是新手最怕的一关。别被名字吓到——它就是"引用的保质期":编译器要保证你手里的引用,不会比它借的数据活得更久(否则就是悬垂引用)。好消息是大多数情况编译器自动推断,你根本不用写。
论什么时候才需要手写生命周期
当函数返回一个引用,而这个引用从多个参数里来的时候,编译器不知道该返回哪个参数的"保质期",才需要你手动标注 'a。就这一种典型场景。除此之外,省略规则基本都能搞定。
经典例子:longest
// 返回两个字符串切片中较长的那个
// 返回值 &str 到底借的是谁?编译器不知道,所以要标注 'a
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() { x } else { y }
}
fn main() {
let s1 = String::from("长字符串");
let result;
{
let s2 = String::from("短");
result = longest(&s1, &s2);
println!("较长的是 {}", result); // 在 s2 还活着时用,OK
}
// println!("{}", result); // 放这就报错:s2 已 drop,result 悬垂
}
那个 <'a> 的意思是:x、y、返回值三者引用的数据,都活在同一个叫 'a 的有效期里,返回值的保质期等于两个参数里较短的那个。这就是编译器逼你"别返回一个马上就死的数据"。
省略规则:大多数情况不用写
论编译器的三条自动推断规则
①每个引用参数都有自己的生命周期。②只有一个输入生命周期时,输出的就用它。③如果有 &self/&mut self 方法,输出的生命周期就跟 self 走。三条走完还判不出来,才要你手动标。所以你看到的 95% 代码都没有 'a。
结构体持有引用:必须标注
// 结构体里存引用,必须告诉编译器它引用的数据活多久
struct ImportantExcerpt<'a> {
part: &'a str,
}
fn main() {
let novel = String::from("很久很久以前");
let excerpt = ImportantExcerpt { part: &novel[0..4] };
println!("{}", excerpt.part);
}
'static 生命周期
&'static str 表示这个引用活满整个程序——字符串字面量就是 &'static str,因为它被编译进二进制、程序结束才释放。日常看到 &'static str 不用慌,就是字面量字符串。
看到 "borrowed value does not live long enough" 怎么办
意思是:你返回的引用指向的数据,在引用还在用的时候就被 drop 了。解决思路:别返回引用,改成返回拥有所有权的 String(return String 而不是 &str),或者把数据挪到外层活得更久的作用域。生命周期报错看着吓人,八成是"引用提前消失"这一类。
另外,Rust 2018 引入的 NLL(Non-Lexical Lifetimes,非词法生命周期)已经把借用规则放宽:引用的有效期到"最后一次使用"就结束,不用等到整个块结束,所以实际比教科书例子宽松得多。