【Rust自学】10.5. 生命周期 Pt.1:生命周期的定义与意义、借用检查器与泛型生命周期

发布时间:2026/7/23 18:51:09
【Rust自学】10.5. 生命周期 Pt.1:生命周期的定义与意义、借用检查器与泛型生命周期 10.5 生命周期 Pt.1生命周期的定义与意义、借用检查器与泛型生命周期10.5.1. 什么是生命周期Rust 中的每个引用都有自己的生命周期。生命周期的作用是让引用保持有效换句话说它就是引用保持有效的作用域。在大多数情况下生命周期是隐式的、可推断的。如果引用的生命周期可能以不同方式相互关联就必须手动标注生命周期。生命周期可以说是 Rust 与其他语言相比最与众不同的特征因此非常难学。10.5.2. 生命周期的存在意义生命周期存在的主要目的是避免悬空引用。这个概念在 4.4. 引用与借用 中已经讨论过这里重复一下之前的解释在使用指针时非常容易触发一种叫做悬空指针Dangling Pointer的错误。其定义如下一个指针引用了内存中的某个地址而这块内存可能已经被释放并重新分配给其他人使用。如果你引用了某些数据Rust 编译器会保证在引用离开作用域之前数据不会离开作用域。这就是 Rust 确保悬空引用永远不会出现的方式。看这个例子fn main() { let r; { // 小花括号 let x 5; r x; } println!({}, r); }在这个例子中先声明了r但没有初始化。目的是让r存在于小花括号外见注释位置的作用域中。当然Rust 没有Null值所以在初始化之前不能使用r。在小花括号内声明了变量x并赋值为5。下一行把x的引用赋给了r。在那个小花括号作用域结束之后在外面打印了r。这段代码是无效的因为打印r时x已经走出作用域并被销毁了。所以r的值——也就是x所引用的内存地址——现在指向的是已经被释放的内存它所指向的数据也不再是x。这就造成了悬空引用因此编译器会报错。输出error[E0597]: x does not live long enough -- src/main.rs:5:7 | 4 | let x 5; | - binding x declared here 5 | r x; | ^^ borrowed value does not live long enough 6 | } | - x dropped here while still borrowed 7 | println!({}, r); | - borrow later used here报错信息说借用的值活得不够长。这是因为内部花括号作用域结束时x走出了作用域但r有更大的作用域并可以继续使用。为了保证程序安全此时任何基于r的操作都无法正确运行。Rust 通过借用检查器来检查代码是否合法。10.5.3. 借用检查器借用检查器通过比较作用域来判断所有借用是否合法。在上面的例子中借用检查器发现r是对x的引用但r的存活时间比x更长因此会报错。怎么解决这个问题呢很简单让x至少活得和r一样长。fn main() { let x 5; let r x; println!({}, r); }在这种情况下x从第 2 行活到第 5 行r从第 3 行活到第 5 行。因此x的生命周期完全覆盖了r的生命周期程序不会报错。10.5.4. 函数中的泛型生命周期看这个例子fn main() { let string1 String::from(abcd); let string2 xyz; let result longest(string1.as_str(), string2); println!(The longest string is {result}); } fn longest(x: str, y: str) - str { if x.len() y.len() { x } else { y } }string1是String而string2是字符串切片str。这两个值被传入longest函数string1需要先转换成str然后打印返回值。longest的逻辑是比较两个输入参数并返回较长的那个。输出error[E0106]: missing lifetime specifier -- src/main.rs:9:33 | 9 | fn longest(x: str, y: str) - str { | ---- ---- ^ expected named lifetime parameter | help: this functions return type contains a borrowed value, but the signature does not say whether it is borrowed from x or y help: consider introducing a named lifetime parameter | 9 | fn longesta(x: a str, y: a str) - a str { | 错误说缺少生命周期标注更具体地说是返回类型缺少生命周期参数。正如help文本所说函数的返回类型包含一个借用值但函数签名没有说明这个借用值来自x还是来自y。考虑引入一个命名生命周期参数。再看这个函数fn longest(x: str, y: str) - str { if x.len() y.len() { x } else { y } }很明显这个函数的返回值要么是x要么是y但无法预先知道是哪一个。如果只看这个函数本身两个输入参数x和y的具体生命周期也是未知的。所以与前面的例子不同我们无法通过比较作用域来判断返回的引用是否会一直有效。借用检查器也做不到因为它不知道返回类型的生命周期是与x绑定还是与y绑定。实际上即使返回值是固定的这样写仍然会报错fn longest(x: str, y: str) - str { x }输出error[E0106]: missing lifetime specifier -- src/main.rs:9:33 | 9 | fn longest(x: str, y: str) - str { | ---- ---- ^ expected named lifetime parameter | help: this functions return type contains a borrowed value, but the signature does not say whether it is borrowed from x or y help: consider introducing a named lifetime parameter | 9 | fn longesta(x: a str, y: a str) - a str { | 编译器仍然无法判断因为函数签名没有表达返回类型中的借用值来自哪里。所以这与函数体内的逻辑无关完全与函数签名有关。该怎么改呢可以按照报错信息中的建议来改 help: this functions return type contains a borrowed value, but the signature does not say whether it is borrowed from x or y help: consider introducing a named lifetime parameter | 9 | fn longesta(x: a str, y: a str) - a str { | 既然它让我们添加泛型生命周期参数我们就添加一个fn longesta(x: a str, y: a str) - a str { if x.len() y.len() { x } else { y } }a表示一个名为a的生命周期。x、y和返回类型都使用生命周期a这意味着x、y和返回值的生命周期是相同的。“相同”这个说法并不完全精确因为main中x和y对应值的实际生命周期其实略有不同。这一点我们会在 10.6 生命周期 Pt.2生命周期的语法与例子 中讨论。现在看完整代码fn main() { let string1 String::from(abcd); let string2 xyz; let result longest(string1.as_str(), string2); println!(The longest string is {result}); } fn longesta(x: a str, y: a str) - a str { if x.len() y.len() { x } else { y } }输出The longest string is abcd