生命周期是 Rust 中最独特的特性之一,它让编译器能够在编译时验证引用的有效性。
生命周期的主要目的是防止悬垂引用:
fn main() {
let r; // r 的生命周期开始
{
let x = 5; // x 的生命周期开始
r = &x; // 错误!x 的生命周期太短
} // x 的生命周期结束
// println!("r: {}", r); // r 是悬垂引用
}Rust 编译器有一个借用检查器(borrow checker),它比较作用域来确保所有的借用都是有效的:
fn main() {
let x = 5; // ----------+-- 'b
// |
let r = &x; // --+-- 'a |
// | |
println!("r: {}", r); // | |
// --+ |
} // ----------+在这个例子中,x 的生命周期 'b 大于 r 的生命周期 'a,所以代码是有效的。
// 这个函数签名没有说明返回的引用与哪个参数相关
// 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("abcd");
let string2 = "xyz";
let result = longest(string1.as_str(), string2);
println!("最长的字符串是 {}", result);
}每个引用都有一个生命周期,即引用保持有效的作用域:
fn main() {
let string1 = String::from("long string is long");
{
let string2 = String::from("xyz");
let result = longest(string1.as_str(), string2.as_str());
println!("最长的字符串是 {}", result);
} // string2 在这里离开作用域
// result 在这里不再有效,因为它可能引用了 string2
}fn main() {
let s1 = String::from("hello");
let s2;
{
let s3 = String::from("world");
s2 = longest(&s1, &s3);
// s2 的生命周期受限于 s3
} // s3 离开作用域
// println!("{}", s2); // 错误!s2 可能引用已释放的内存
}'static 生命周期是特殊的生命周期,它表示引用在整个程序期间都有效:
fn main() {
// 字符串字面量有 'static 生命周期
let s: &'static str = "I have a static lifetime.";
// 也可以显式创建静态生命周期的数据
static HELLO: &str = "Hello, world!";
let r: &'static str = HELLO;
println!("{}", r);
}生命周期标注使用撇号 ' 开头:
&i32 // 引用
&'a i32 // 带有显式生命周期的引用
&'a mut i32 // 带有显式生命周期的可变引用// 单个生命周期参数
fn foo<'a>(x: &'a str) -> &'a str {
x
}
// 多个生命周期参数
fn foo2<'a, 'b>(x: &'a str, y: &'b str) -> &'a str {
x
}
// 可变引用的生命周期
fn foo3<'a>(x: &'a mut i32) {
*x += 1;
}Rust 有三条生命周期省略规则,使得在常见情况下不需要显式标注:
- 第一条规则:每个引用参数都有自己的生命周期参数
- 第二条规则:如果只有一个输入生命周期参数,它被赋予所有输出生命周期参数
- 第三条规则:如果方法有多个输入生命周期参数,但其中一个是
&self或&mut self,那么self的生命周期被赋给所有输出生命周期参数
// 应用规则前
fn first_word(s: &str) -> &str {
// ...
}
// 应用第一条规则
fn first_word<'a>(s: &'a str) -> &str {
// ...
}
// 应用第二条规则
fn first_word<'a>(s: &'a str) -> &'a str {
// ...
}// 存储引用的结构体需要生命周期标注
struct ImportantExcerpt<'a> {
part: &'a str,
}
impl<'a> ImportantExcerpt<'a> {
fn level(&self) -> i32 {
3
}
// 生命周期省略规则适用
fn announce_and_return_part(&self, announcement: &str) -> &str {
println!("请注意: {}", announcement);
self.part
}
}
fn main() {
let novel = String::from("Call me Ishmael. Some years ago...");
let first_sentence = novel.split('.').next().expect("Could not find a '.'");
let i = ImportantExcerpt {
part: first_sentence,
};
}// 'a 必须比 'b 活得长
fn foo<'a, 'b: 'a>(x: &'a str, y: &'b str) -> &'a str {
x
}
// 在结构体中使用生命周期约束
struct Ref<'a, T: 'a> {
field: &'a T,
}
// 多个约束
fn bar<'a, 'b, 'c>(x: &'a str, y: &'b str) -> &'c str
where
'a: 'c,
'b: 'c,
{
if x.len() > y.len() {
x
} else {
y
}
}// 返回值的生命周期与输入参数相关
fn longest_with_an_announcement<'a, T>(
x: &'a str,
y: &'a str,
ann: T,
) -> &'a str
where
T: Display,
{
println!("公告: {}", ann);
if x.len() > y.len() {
x
} else {
y
}
}
// 使用示例
fn main() {
let string1 = String::from("long string is long");
let result;
{
let string2 = String::from("xyz");
result = longest_with_an_announcement(
string1.as_str(),
string2.as_str(),
"今天是个好日子",
);
println!("最长的字符串是: {}", result);
}
// println!("{}", result); // 错误!result 的生命周期受限于 string2
}struct Context<'a>(&'a str);
struct Parser<'a> {
context: &'a Context<'a>,
}
impl<'a> Parser<'a> {
fn parse(&self) -> Result<(), &'a str> {
Err(&self.context.0[1..])
}
}
fn parse_context(context: Context) -> Result<(), &str> {
Parser { context: &context }.parse()
}
// 更复杂的例子
struct StrSplit<'a, 'b> {
remainder: Option<&'a str>,
delimiter: &'b str,
}
impl<'a, 'b> Iterator for StrSplit<'a, 'b> {
type Item = &'a str;
fn next(&mut self) -> Option<Self::Item> {
let remainder = self.remainder.as_mut()?;
if let Some(next_delim) = remainder.find(self.delimiter) {
let until_delimiter = &remainder[..next_delim];
*remainder = &remainder[(next_delim + self.delimiter.len())..];
Some(until_delimiter)
} else {
self.remainder.take()
}
}
}
fn str_split<'a, 'b>(haystack: &'a str, delimiter: &'b str) -> StrSplit<'a, 'b> {
StrSplit {
remainder: Some(haystack),
delimiter,
}
}
fn main() {
let haystack = "a b c d e";
let letters: Vec<_> = str_split(haystack, " ").collect();
println!("{:?}", letters);
}fn make_a_cloner<'a>(s: &'a str) -> impl Fn() -> &'a str {
move || s
}
fn main() {
let s = String::from("hello");
let cloner = make_a_cloner(&s);
let s_ref = cloner();
println!("{}", s_ref);
drop(s); // s 被释放后,cloner 不能再使用
// let s_ref2 = cloner(); // 错误!
}// Higher-Ranked Trait Bounds (HRTB)
fn call_with_ref<F>(f: F)
where
F: for<'a> Fn(&'a str) -> &'a str,
{
let s = String::from("hello");
let result = f(&s);
println!("{}", result);
}
fn main() {
call_with_ref(|s| s);
call_with_ref(|s| &s[1..]);
}// 编译器可以推断生命周期
fn foo(x: &str, y: &str) -> &str {
if x.len() > y.len() {
x
} else {
y
}
}
// 但在某些情况下需要显式标注
struct Wrapper<'a> {
value: &'a str,
}
impl<'a> Wrapper<'a> {
fn new(value: &'a str) -> Self {
Wrapper { value }
}
// 这里需要显式的生命周期
fn combine<'b>(&'a self, other: &'b str) -> &'a str
where
'b: 'a
{
if self.value.len() > other.len() {
self.value
} else {
other
}
}
}use std::fmt::Display;
// 一个缓存系统的例子
struct Cache<'a, T> {
data: Vec<&'a T>,
}
impl<'a, T: Display> Cache<'a, T> {
fn new() -> Self {
Cache { data: Vec::new() }
}
fn insert(&mut self, value: &'a T) {
self.data.push(value);
}
fn get(&self, index: usize) -> Option<&&'a T> {
self.data.get(index)
}
fn display_all(&self) {
for (i, item) in self.data.iter().enumerate() {
println!("Cache[{}]: {}", i, item);
}
}
}
fn main() {
let value1 = String::from("Hello");
let value2 = String::from("World");
let mut cache = Cache::new();
cache.insert(&value1);
cache.insert(&value2);
cache.display_all();
// value1 和 value2 必须比 cache 活得长
drop(cache);
println!("Values still exist: {} {}", value1, value2);
}// 1. 尽可能使用生命周期省略
fn process(input: &str) -> &str {
&input[1..]
}
// 2. 必要时才添加生命周期标注
fn process_two<'a>(first: &'a str, second: &str) -> &'a str {
// 只返回 first,所以只需要 'a
first
}
// 3. 避免不必要的生命周期约束
// 不好的做法
fn bad<'a, 'b: 'a>(x: &'a str, y: &'b str) -> &'a str {
x
}
// 好的做法
fn good<'a>(x: &'a str, y: &str) -> &'a str {
x
}
// 4. 使用结构体时明确生命周期关系
struct Document<'a> {
content: &'a str,
}
struct DocumentProcessor<'a> {
current_doc: Option<Document<'a>>,
}
impl<'a> DocumentProcessor<'a> {
fn process(&mut self, doc: Document<'a>) {
// 处理文档
self.current_doc = Some(doc);
}
}本章深入学习了 Rust 的生命周期系统:
- 核心思想:生命周期确保引用始终有效,防止悬垂引用
- 生命周期标注:使用
'a等符号标注引用的生命周期 - 省略规则:编译器可以在常见情况下推断生命周期
- 实际应用:在复杂场景中正确使用生命周期标注
生命周期是 Rust 保证内存安全的关键机制之一,虽然初学时可能觉得复杂,但随着实践会逐渐理解其价值。下一章我们将学习特征对象,探讨 Rust 中的动态分发机制。