Rust 入门 生命周期
生命周期
- Rust 中的每一个引用都有其 生命周期(lifetime)
- 生命周期就是引用保持有效的作用域
- 大多数情况下: 声明周期是隐式的, 可推断的
- 当引用的声明周期可能以不同的方式互相关联时, 需要我们手动标注生命周期
声明周期避免了悬垂引用
-
生命周期的主要作用就是为了避免悬垂引用
-
示例: 尝试使用离开作用域的值的引用
fn main() {
let r;
{
let x = 5;
r = &x;
}
println!("r: {}", r);
}
// 外部作用域声明了一个没有初化的变量 r
// 而内部作用域声明了一个初值为 5 的变量 x
// 在内部作用域中,我们尝试将 r 的值设置为一个 x 的引用
// 接着在内部作用域结束后,尝试打印出 r 的值。
// 这段代码不能编译因为 r 引用的值在尝试使用之前就离开了作用域
// 如下是错误信息:
// error[E0597]: `x` does not live long enough
// --> src/main.rs:7:5
// |
// 6 | r = &x;
// | - borrow occurs here
// 7 | }
// | ^ `x` dropped here while still borrowed
// ...
// 10 | }
// | - borrowed value needs to live until here
// 变量 x 没有 “存在的足够久”
// 其原因是 x 在到达第 7 行内部作用域结束时就离开了作用域
// 然而 r 在外部作用域仍是有效的
// 作用域越大我们就说它 “存在的越久”
// 如果 Rust 允许这段代码工作
// r 将会引用在 x 离开作用域时 被释放的内存
// 这时尝试对 r 做任何操作都不能正常工作
借用检查器
- Rust 编译器有一个 借用检查器(borrow checker)
- 它比较作用域来确保所有的借用都是有效的
- 示例: 尝试使用离开作用域的值的引用 (生命周期标注)
fn main() {
let r; // ---------+-- 'a
// |
{ // |
let x = 5; // -+-- 'b |
r = &x; // | |
} // -+ |
// |
println!("r: {}", r); // |
} // ---------+
// 将 r 的生命周期标记为 'a
// 将 x 的生命周期标记为 'b
// 可以看到 'b 块比 'a 小
// 在编译时, 借用检查器会比较这两个生命周期的大小
// 发现 r 拥有生命周期 'a 但它引用了一个拥有生命周期 'b 的对象
// 此时程序被拒绝编译, 因为生命周期 'b 比生命周期 'a 要小
// 也就是被引用的对象比它的引用者存在的时间更短。
- 示例: 修改上述代码使其编译通过
fn main() {
let x = 5; // ----------+-- 'b
// |
let r = &x; // --+-- 'a |
// | |
println!("r: {}", r); // | |
// | |
} // --+-------+
// 将 r 的生命周期标记为 'a
// 将 x 的生命周期标记为 'b
// 此时 'b 比 'a 大
// 也就说明了在引用被使用前是有效的
函数中的泛型生命周期
- 示例: 比较字符串长度
/// 比较字符串的长度, 返回较长的字符串引用
fn longest(x: &str, y: &str) -> &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!("The longest string is {}", result);
}
// 如下是错误信息:
// error[E0106]: missing lifetime specifier
// --> src/main.rs:1:33
// |
// 1 | fn longest(x: &str, y: &str) -> &str {
// | ^ expected lifetime parameter
// |
// = help: this function's return type contains a borrowed value, but the
// signature does not say whether it is borrowed from `x` or `y`
// 编译器此时提示我们返回值需要泛型声明周期参数
// 因为 rust 不知道返回值指向的是参数x还是参数y
- 示例: 修改上述代码使其编译通过
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() {
x
} else {
y
}
}
// 我们新增了一个泛型参数 'a, 表示这个函数存在一个叫做 'a 的生命周期
// 在引用符号(&)后加上生命周期名称, 就代表生命周期标注
// 这段代码表示的意思是: [x, y, 返回值]都包含相同的声明周期, 但是它们三个的生命周期并不相同
生命周期标注
- 生命周期标注不会改变引用的生命周期长度
- 当指定了泛型生命周期参数后, 函数可以接受带有任何生命周期的的引用
- 生命周期标注: 描述多个引用的生命周期相互的关系, 不会影响其引用的生命周期
生命周期参数名称
- 以
'
开头 (撇号 也叫单引号) - 通常使用全小写的命名
- 非常短, 通常使用一个字母表示
生命周期标注的位置
- 在引用符号(
&
)之后 - 使用空格将标注和类型分开
示例
&i32
一个引用&'a i32
带有显示标注生命周期的引用&'a mut i32
带有显示标注生命周期的可变引用
函数签名中的生命周期标注
- 在函数签名中必须先定义泛型生命周期参数
- 示例:
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
}
// 首先在 <> 中声明了一个泛型生命周期参数
// x 和 y 的数据类型为 &'a str
// 返回值的类型为 &'a str
// 'a 描述了: x 和 y 的的声明周期不能比 'a 短
深入理解生命周期
- 指定生命周期参数的方式依赖于函数所做的事情
- 示例:
fn longest<'a>(x: &'a str, y: &str) -> &'a str {
x
}
// 此函数中的y与返回值无关
- 从函数中返回引用时, 返回类型的生命周期参数必须与参数中的生命周期相匹配
结构体中的生命周期标注
- 结构体中的字段可以是自持有的类型
- 结构体中的字段也可以是引用类型
- 若结构体中的字段是引用类型
- 需要手动添加声明周期标注
- 例子:
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("Could not find a '.'");
let _i = ImportantExcerpt { part: first_sentence };
}
生命周期的省略
- 在 Rust 语言的早期版本中, 每个函数都必须显示的标注生命周期
- 后来 Rust 的开发团队发现有些情况下生命周期是可以被推断的
于是 Rust 开发团队将一些可以推断声明周期的情况写入借用检查器中 - 例子: 早期 Rust 的函数生命周期标注(pre-1.0)
fn first_word<'a>(s: &'a str) -> &'a str {
}
- 例子: 现在 Rust 的函数声明周期标注中的省略
fn first_word(s: &str) -> &str {
}
- 需要注意的是: 不是所有的生命周期标注都可以省略
声明周期的省略规则
- 被编码进 Rust 引用分析的模式被称为 生命周期省略规则(lifetime elision rules)
- 这些规则不需要开发者来遵守
- 这是一些特殊情况, 需要由编译器来考虑
- 如果你的代码符合这些情况, 就不需要显示的标注生命周期
- 声明周期省略规则不会提供完整的推断
- 如果应用规则后, 引用的声明周期仍然模糊不清, 就会导致编译错误
- 解决办法就是手动添加声明周期标注
- 输入/输出声明周期:
- 函数或方法的参数的生命周期被称为 输入生命周期(input lifetimes)
- 返回值的生命周期被称为 输出生命周期(output lifetimes)
- 声明周期省略规则:
- 每个引用类型的参数都有自己的声明周期
- 如果只有一个输入声明周期参数, 那么该生命周期会被赋予所有的输出声明周期参数
- 如果输入生命周期参数中包含
&self
或者&mut self
, 那么self
的声明周期会被赋给所有的输出声明周期参数
- 编译器使用上述三条规则在没有显式标注声明周期的情况下,来确定引动的声明周期
- 第一条规则用于输入生命周期
- 后两条规则用于输出声明周期
- 如果编译器使用了这三个规则之后仍然无法确定声明周期的引用, 就会编译失败
- 这三条适用于
fn
定义和impl
中
方法定义中的生命周期标注
-
在结构体上使用生命周期实现方法, 语法和泛型相同
-
在哪声明和使用生命周期参数, 依赖于:
- 生命周期参数是否和字段, 方法的参数或返回值有关
-
结构体字段的生命周期名:
- 在
impl
后声明 - 在结构体名称后使用
- 这些生命周期是结构体类型的一部分
- 在
-
实现块内的方法签名中:
- 引用必须绑定结构体字段的声明周期, 或者引用独立
- 生命周期省略规则经常使得方法中的生命周期标注不是必须的
-
例子:
struct ImportantExcerpt<'a> {
part: &'a str,
}
impl<'a> ImportantExcerpt<'a> {
fn level(&self) -> i32 {
3
}
}
静态声明周期
'static
是一个特殊的声明周期- 它表示整个程序的运行时间
- 例如:
- 所有的字符串字面值就是
'static
- 所有的字符串字面值就是
- 在为引用指定
'static
之前一定要三思
例子:
- 在这个例子中使用了:
- 泛型参数类型
- 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!("Announcement! {}", ann);
if x.len() > y.len() {
x
} else {
y
}
}
fn main() {
}
如果对我分享的内容感兴趣的话 记得关注我得动态
求推荐 求收藏 求转发 求关注
求推荐 求收藏 求转发 求关注