生命周期
生命周期是 Rust 另一个独特的概念。它帮助编译器确保所有引用都是有效的,防止悬垂引用。
为什么需要生命周期?
Section titled “为什么需要生命周期?”考虑以下代码:
fn longest(x: &str, y: &str) -> &str { if x.len() > y.len() { x } else { y }}这段代码无法编译。为什么?
编译器不知道返回的引用是来自 x 还是 y,因此无法确定返回值的有效期。返回的引用必须在被使用时仍然有效,但编译器无法保证这一点。
fn main() { let string1 = String::from("long string"); let result; { let string2 = String::from("xyz"); result = longest(&string1, &string2); } // 如果 result 是 string2 的引用,这里就是悬垂引用! // println!("{}", result);}生命周期标注语法
Section titled “生命周期标注语法”生命周期标注不会改变引用的存活时间,只是描述多个引用之间的关系。
生命周期参数以 ' 开头,通常使用小写字母:
&i32 // 引用&'a i32 // 带有生命周期 'a 的引用&'a mut i32 // 带有生命周期 'a 的可变引用函数中的生命周期
Section titled “函数中的生命周期”fn longest<'a>(x: &'a str, y: &'a str) -> &'a str { if x.len() > y.len() { x } else { y }}这段代码的含义:
<'a>声明一个生命周期参数x: &'a str和y: &'a str表示两个参数有相同的生命周期-> &'a str表示返回值的生命周期与参数相同
实际效果:返回值的生命周期是 x 和 y 生命周期中较短的那个。
fn main() { let string1 = String::from("long string is long");
{ let string2 = String::from("xyz"); let result = longest(&string1, &string2); println!("最长的字符串是: {}", result); // OK }
// println!("{}", result); // 错误!result 在这里无效}不同的生命周期参数
Section titled “不同的生命周期参数”有时参数有不同的生命周期:
fn first<'a, 'b>(x: &'a str, y: &'b str) -> &'a str { x // 只返回 x,所以返回值生命周期只与 x 相关}生命周期省略规则
Section titled “生命周期省略规则”Rust 编译器可以在某些情况下推断生命周期,不需要显式标注。这些规则称为生命周期省略规则。
规则 1:每个引用参数都有自己的生命周期
Section titled “规则 1:每个引用参数都有自己的生命周期”fn foo(x: &str) -> ...// 等价于fn foo<'a>(x: &'a str) -> ...
fn foo(x: &str, y: &str) -> ...// 等价于fn foo<'a, 'b>(x: &'a str, y: &'b str) -> ...规则 2:如果只有一个输入生命周期参数,它被赋给所有输出生命周期
Section titled “规则 2:如果只有一个输入生命周期参数,它被赋给所有输出生命周期”fn foo(x: &str) -> &str// 等价于fn foo<'a>(x: &'a str) -> &'a str规则 3:如果有 &self 或 &mut self,self 的生命周期被赋给所有输出生命周期
Section titled “规则 3:如果有 &self 或 &mut self,self 的生命周期被赋给所有输出生命周期”impl Foo { fn method(&self, x: &str) -> &str // 等价于 fn method<'a, 'b>(&'a self, x: &'b str) -> &'a str}规则应用示例
Section titled “规则应用示例”// 不需要标注(规则 2)fn first_word(s: &str) -> &str { let bytes = s.as_bytes(); for (i, &item) in bytes.iter().enumerate() { if item == b' ' { return &s[..i]; } } &s[..]}
// 需要标注(规则无法确定)fn longest<'a>(x: &'a str, y: &'a str) -> &'a str { if x.len() > y.len() { x } else { y }}结构体中的生命周期
Section titled “结构体中的生命周期”当结构体持有引用时,必须标注生命周期:
struct ImportantExcerpt<'a> { part: &'a str,}
fn main() { let novel = String::from("Call me Ishmael. Some years ago..."); let first_sentence = novel.split('.').next().unwrap();
let excerpt = ImportantExcerpt { part: first_sentence, };
println!("{}", excerpt.part);}这个标注意味着:ImportantExcerpt 实例的生命周期不能超过它持有的引用 part。
结构体方法中的生命周期
Section titled “结构体方法中的生命周期”impl<'a> ImportantExcerpt<'a> { // 规则 3:返回值生命周期与 self 相同 fn level(&self) -> i32 { 3 }
// 规则 3:返回值生命周期与 self 相同 fn announce_and_return_part(&self, announcement: &str) -> &str { println!("请注意: {}", announcement); self.part }}静态生命周期
Section titled “静态生命周期”'static 是一个特殊的生命周期,表示引用在整个程序运行期间都有效:
let s: &'static str = "我有静态生命周期";所有字符串字面量都有 'static 生命周期,因为它们被硬编码在程序的二进制文件中。
谨慎使用 ‘static
Section titled “谨慎使用 ‘static”不要轻易使用 'static,大多数情况下编译器提示需要 'static 是因为:
- 试图创建悬垂引用
- 生命周期不匹配
先检查这些问题,而不是直接加 'static。
泛型、Trait Bound 与生命周期
Section titled “泛型、Trait Bound 与生命周期”三者可以结合使用:
use std::fmt::Display;
fn longest_with_announcement<'a, T>( x: &'a str, y: &'a str, ann: T,) -> &'a strwhere T: Display,{ println!("公告: {}", ann); if x.len() > y.len() { x } else { y }}常见生命周期模式
Section titled “常见生命周期模式”模式 1:返回输入的引用
Section titled “模式 1:返回输入的引用”fn first<'a>(x: &'a str, _y: &str) -> &'a str { x}模式 2:返回新创建的数据(不需要生命周期)
Section titled “模式 2:返回新创建的数据(不需要生命周期)”fn create_string() -> String { String::from("hello")}模式 3:返回引用会失败的情况
Section titled “模式 3:返回引用会失败的情况”// 错误!不能返回局部变量的引用fn invalid<'a>() -> &'a str { let s = String::from("hello"); // &s // 错误!s 在函数结束时被释放 "hello" // 这样可以,字符串字面量是 'static}练习 1:修复生命周期错误
Section titled “练习 1:修复生命周期错误”以下代码无法编译,请添加正确的生命周期标注:
fn longest(x: &str, y: &str) -> &str { if x.len() > y.len() { x } else { y }}
fn main() { let s1 = String::from("hello"); let s2 = String::from("world!"); let result = longest(&s1, &s2); println!("最长的是: {}", result);}练习 2:理解生命周期省略
Section titled “练习 2:理解生命周期省略”解释为什么以下函数不需要生命周期标注:
fn first_word(s: &str) -> &str { let bytes = s.as_bytes(); for (i, &item) in bytes.iter().enumerate() { if item == b' ' { return &s[..i]; } } &s[..]}提示:参考生命周期省略规则。
练习 3:结构体生命周期
Section titled “练习 3:结构体生命周期”创建一个结构体 Excerpt,持有一个字符串切片,正确标注生命周期:
// 定义结构体
fn main() { let text = String::from("The quick brown fox jumps over the lazy dog."); let first_part = text.split('.').next().unwrap(); let excerpt = /* 创建实例 */; println!("{}", excerpt./* 字段名 */);}练习 4:复杂生命周期
Section titled “练习 4:复杂生命周期”以下代码能否编译?为什么?
fn main() { let string1 = String::from("xyz"); let result; { let string2 = String::from("long string is long"); result = longest(&string1, &string2); } println!("最长的字符串是: {}", result);}
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str { if x.len() > y.len() { x } else { y }}思考:
result的生命周期是什么?- 为什么编译器会报错(或不报错)?
掌握了生命周期后,下一章我们将学习 Rust 中常用的数据结构。