====== 第十四章 生命周期 ======
===== 14.1 为什么需要生命周期 =====
Rust需要在编译时确保所有引用都是有效的。生命周期就是用来追踪引用的有效范围的。
**核心问题:** 编译器如何知道引用是否指向有效的数据?
{
let r; // ---------+-- 'a
// |
{ // |
let x = 5; // -+-- 'b |
r = &x; // | |
} // -+ |
// |
println!("r: {}", r); // |
} // ---------+
这段代码编译失败,因为r的生命周期'a比x的生命周期'b长。
===== 14.2 生命周期标注语法 =====
==== 标注规则 ====
* 以'开头,通常小写且简短,如'a、'b
* 放在&后面,如&'a i32
* 只是描述引用之间的关系,不改变实际生命周期
==== 示例 ====
&i32 // 引用
&'a i32 // 带有显式生命周期的引用
&'a mut i32 // 带有显式生命周期的可变引用
===== 14.3 函数中的生命周期 =====
==== 生命周期省略规则 ====
Rust有**生命周期省略规则**,在简单情况下自动推断:
1. 每个引用参数都有自己的生命周期
2. 如果只有一个输入生命周期,它赋给所有输出生命周期
3. 如果有多个输入生命周期,但一个是&self或&mut self,self的生命周期赋给输出
==== 显式标注示例 ====
// 编译错误:编译器无法推断返回值的生命周期
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
}
}
**含义:** 返回的引用与x和y中生命周期较短的那个相同。
==== 使用示例 ====
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);
}
}
==== 错误使用示例 ====
fn main() {
let string1 = String::from("long string is long");
let result;
{
let string2 = String::from("xyz");
result = longest(string1.as_str(), string2.as_str());
}
println!("最长的字符串是{}", result); // 错误!string2已失效
}
===== 14.4 结构体中的生命周期 =====
==== 含引用的结构体 ====
如果结构体包含引用,必须标注生命周期:
struct ImportantExcerpt<'a> {
part: &'a str,
}
fn main() {
let novel = String::from("Call me Ishmael. Some years ago...");
let first_sentence = novel.split('.').next().expect("找不到.");
let i = ImportantExcerpt {
part: first_sentence,
};
println!("{}", i.part);
}
**含义:** ImportantExcerpt的实例不能比part中的引用活得更长。
===== 14.5 生命周期的省略 =====
==== 规则1:每个引用参数有自己的生命周期 ====
fn foo<'a>(x: &'a i32) // 一个参数
fn foo<'a, 'b>(x: &'a i32, y: &'b i32) // 两个参数
==== 规则2:单个输入生命周期赋给输出 ====
fn foo<'a>(x: &'a i32) -> &'a i32 // 省略写法:fn foo(x: &i32) -> &i32
==== 规则3:&self的生命周期赋给输出 ====
impl<'a> ImportantExcerpt<'a> {
fn level(&self) -> i32 { // 省略写法
3
}
}
===== 14.6 静态生命周期 =====
**'static**表示整个程序运行期间都有效。
let s: &'static str = "我有静态生命周期";
**注意:**
* 字符串字面量有'static生命周期
* 不要盲目使用'static来解决生命周期错误
* 通常意味着设计问题
===== 14.7 泛型类型参数、Trait Bound和生命周期 =====
use std::fmt::Display;
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
}
}
===== 14.8 生命周期实践 =====
==== 返回引用时的生命周期 ====
struct Parser<'a> {
content: &'a str,
}
impl<'a> Parser<'a> {
fn new(content: &'a str) -> Self {
Parser { content }
}
fn first_word(&self) -> &'a str {
self.content.split_whitespace().next().unwrap_or("")
}
}
==== 多个生命周期 ====
fn split<'a, 'b>(input: &'a str, delimiter: &'b str) -> Vec<&'a str> {
input.split(delimiter).collect()
}
===== 练习题 =====
==== 练习题14.1:修复生命周期错误 ====
fn get_first_word(s: &str) -> &str {
s.split_whitespace().next().unwrap_or("")
}
fn main() {
let text = String::from("hello world");
let word = get_first_word(&text);
println!("{}", word);
}
**答案:** 使用生命周期省略规则即可,不需要显式标注。
==== 练习题14.2:实现包含引用的结构体 ====
struct Config<'a> {
filename: &'a str,
content: &'a str,
}
impl<'a> Config<'a> {
fn new(filename: &'a str, content: &'a str) -> Self {
Config { filename, content }
}
fn display(&self) {
println!("文件:{}", self.filename);
println!("内容:{}", self.content);
}
}
fn main() {
let filename = "config.txt";
let content = "key=value";
let config = Config::new(filename, content);
config.display();
}
===== 本章小结 =====
本章学习了Rust的生命周期:
* **生命周期的目的**:确保引用总是有效
* **标注语法**:'a、&'a T
* **函数生命周期**:返回值与输入参数关联
* **结构体生命周期**:含引用的结构体需要标注
* **省略规则**:简化常见情况的标注
* **'static**:整个程序运行期间有效
生命周期是Rust内存安全保证的重要组成部分,虽然起初难以理解,但掌握后能写出更安全的代码。